diff --git a/src/webgl/material.js b/src/webgl/material.js index 854917c7de..e94d148538 100644 --- a/src/webgl/material.js +++ b/src/webgl/material.js @@ -1279,14 +1279,17 @@ p5.prototype.metalness = function (metallic) { * @private blends colors according to color components. * If alpha value is less than 1, or non-standard blendMode * we need to enable blending on our gl context. - * @param {Number[]} color [description] - * @return {Number[]} Normalized numbers array + * @param {Number[]} color The currently set color, with values in 0-1 range + * @param {Boolean} [hasTransparency] Whether the shape being drawn has other + * transparency internally, e.g. via vertex colors + * @return {Number[]]} Normalized numbers array */ -p5.RendererGL.prototype._applyColorBlend = function (colors) { +p5.RendererGL.prototype._applyColorBlend = function(colors, hasTransparency) { const gl = this.GL; const isTexture = this.drawMode === constants.TEXTURE; const doBlend = + hasTransparency || this.userFillShader || this.userStrokeShader || this.userPointShader || diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js index 18731dfcb5..a1159d244b 100644 --- a/src/webgl/p5.Geometry.js +++ b/src/webgl/p5.Geometry.js @@ -66,6 +66,9 @@ p5.Geometry = class Geometry { this.detailY = detailY !== undefined ? detailY : 1; this.dirtyFlags = {}; + this._hasFillTransparency = undefined; + this._hasStrokeTransparency = undefined; + if (callback instanceof Function) { callback.call(this); } @@ -73,6 +76,9 @@ p5.Geometry = class Geometry { } reset() { + this._hasFillTransparency = undefined; + this._hasStrokeTransparency = undefined; + this.lineVertices.clear(); this.lineTangentsIn.clear(); this.lineTangentsOut.clear(); @@ -88,6 +94,32 @@ p5.Geometry = class Geometry { this.dirtyFlags = {}; } + + hasFillTransparency() { + if (this._hasFillTransparency === undefined) { + this._hasFillTransparency = false; + for (let i = 0; i < this.vertexColors.length; i += 4) { + if (this.vertexColors[i + 3] < 1) { + this._hasFillTransparency = true; + break; + } + } + } + return this._hasFillTransparency; + } + hasStrokeTransparency() { + if (this._hasStrokeTransparency === undefined) { + this._hasStrokeTransparency = false; + for (let i = 0; i < this.lineVertexColors.length; i += 4) { + if (this.lineVertexColors[i + 3] < 1) { + this._hasStrokeTransparency = true; + break; + } + } + } + return this._hasStrokeTransparency; + } + /** * Removes the internal colors of p5.Geometry. * Using `clearColors()`, you can use `fill()` to supply new colors before drawing each shape. diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js index 673fe0521e..fbe5472561 100644 --- a/src/webgl/p5.RendererGL.Immediate.js +++ b/src/webgl/p5.RendererGL.Immediate.js @@ -519,7 +519,10 @@ p5.RendererGL.prototype._drawImmediateFill = function(count = 1) { } shader.disableRemainingAttributes(); - this._applyColorBlend(this.curFillColor); + this._applyColorBlend( + this.curFillColor, + this.immediateMode.geometry.hasFillTransparency() + ); if (count === 1) { gl.drawArrays( @@ -561,7 +564,10 @@ p5.RendererGL.prototype._drawImmediateStroke = function() { buff._prepareBuffer(this.immediateMode.geometry, shader); } shader.disableRemainingAttributes(); - this._applyColorBlend(this.curStrokeColor); + this._applyColorBlend( + this.curStrokeColor, + this.immediateMode.geometry.hasFillTransparency() + ); gl.drawArrays( gl.TRIANGLES, diff --git a/src/webgl/p5.RendererGL.Retained.js b/src/webgl/p5.RendererGL.Retained.js index d278d51428..9bd05739c8 100644 --- a/src/webgl/p5.RendererGL.Retained.js +++ b/src/webgl/p5.RendererGL.Retained.js @@ -143,7 +143,10 @@ p5.RendererGL.prototype.drawBuffers = function(gId) { //vertex index buffer this._bindBuffer(geometry.indexBuffer, gl.ELEMENT_ARRAY_BUFFER); } - this._applyColorBlend(this.curFillColor); + this._applyColorBlend( + this.curFillColor, + geometry.model.hasFillTransparency() + ); this._drawElements(gl.TRIANGLES, gId); fillShader.unbindShader(); } @@ -156,7 +159,10 @@ p5.RendererGL.prototype.drawBuffers = function(gId) { buff._prepareBuffer(geometry, strokeShader); } strokeShader.disableRemainingAttributes(); - this._applyColorBlend(this.curStrokeColor); + this._applyColorBlend( + this.curStrokeColor, + geometry.model.hasStrokeTransparency() + ); this._drawArrays(gl.TRIANGLES, gId); strokeShader.unbindShader(); } diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index 73cbdc87ba..e747b12d36 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -1258,6 +1258,30 @@ suite('p5.RendererGL', function() { assert.deepEqual(myp5.get(16, 16), [255, 0, 255, 255]); done(); }); + + test('transparency works the same with per-vertex colors', function() { + myp5.createCanvas(20, 20, myp5.WEBGL); + myp5.noStroke(); + + function drawShapes() { + myp5.fill(255, 0, 0, 100); + myp5.rect(-10, -10, 15, 15); + myp5.fill(0, 0, 255, 100); + myp5.rect(-5, -5, 15, 15); + } + + drawShapes(); + myp5.loadPixels(); + const eachShapeResult = [...myp5.pixels]; + + myp5.clear(); + const shapes = myp5.buildGeometry(drawShapes); + myp5.model(shapes); + myp5.loadPixels(); + const singleShapeResult = [...myp5.pixels]; + + assert.deepEqual(eachShapeResult, singleShapeResult); + }); }); suite('BufferDef', function() {