Repository: mrdoob/three.js (JavaScript)

Issue:
Add missing blend function constants
<!-- Ignoring this template may result in your feature request getting deleted -->

**Is your feature request related to a problem? Please describe.**

I have some custom blending logic that would greatly benefit from the missing blend function constants:

- gl.CONSTANT_COLOR
- gl.ONE_MINUS_CONSTANT_COLOR
- gl.CONSTANT_ALPHA
- gl.ONE_MINUS_CONSTANT_ALPHA

**Describe the solution you'd like**

Add the missing blend function constants. Proposed as:

- `THREE.ConstantColor`
- `THREE.OneMinusConstantColor`
- `THREE.ConstantAlpha`
- `THREE.OneMinusConstantAlpha`

And add a complimenting field to `THREE.Material`, proposed as:
```
material.blendColor
```
or
```
material.setBlendColor(color)
```
that maps to the underlying WebGL `gl.blendColor` method.

We might also want to handle this case (from the spec):
```
"If a constant color and a constant alpha value are used together as source and destination factors, a gl.INVALID_ENUM error is thrown."
```

I can look into this if it is deemed by maintainers to be a good addition.


Discussion:

Patch:
diff --git a/docs/api/en/constants/CustomBlendingEquations.html b/docs/api/en/constants/CustomBlendingEquations.html
index f6e9d78d14f4c3..8f99f73ebc910c 100644
--- a/docs/api/en/constants/CustomBlendingEquations.html
+++ b/docs/api/en/constants/CustomBlendingEquations.html
@@ -52,6 +52,10 @@ <h2>Source Factors</h2>
 		THREE.DstColorFactor
 		THREE.OneMinusDstColorFactor 
 		THREE.SrcAlphaSaturateFactor
+		THREE.ConstantColorFactor
+		THREE.OneMinusConstantColorFactor
+		THREE.ConstantAlphaFactor
+		THREE.OneMinusConstantAlphaFactor
 		</code>
 
 		<h2>Destination Factors</h2>
diff --git a/docs/api/en/materials/Material.html b/docs/api/en/materials/Material.html
index d84429199c852b..f4fd9234ddbd61 100644
--- a/docs/api/en/materials/Material.html
+++ b/docs/api/en/materials/Material.html
@@ -49,6 +49,20 @@ <h3>[property:Boolean alphaToCoverage]</h3>
 			`true`). Default is `false`.
 		</p>
 
+		<h3>[property:Float blendAlpha]</h3>
+		<p>
+			Represents the alpha value of the constant blend color. Default is `0`.
+
+			This property has only an effect when using custom blending with [page:CustomBlendingEquation ConstantAlpha] or [page:CustomBlendingEquation OneMinusConstantAlpha].
+		</p>
+
+		<h3>[property:Color blendColor]</h3>
+		<p>
+			Represent the RGB values of the constant blend color. Default is `0x000000`.<br />
+
+			This property has only an effect when using custom blending with [page:CustomBlendingEquation ConstantColor] or [page:CustomBlendingEquation OneMinusConstantColor].
+		</p>
+
 		<h3>[property:Integer blendDst]</h3>
 		<p>
 			Blending destination. Default is [page:CustomBlendingEquation OneMinusSrcAlphaFactor]. 
diff --git a/src/constants.js b/src/constants.js
index 57a4c11eaa5d91..94b1a2500380f6 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -36,6 +36,10 @@ export const OneMinusDstAlphaFactor = 207;
 export const DstColorFactor = 208;
 export const OneMinusDstColorFactor = 209;
 export const SrcAlphaSaturateFactor = 210;
+export const ConstantColorFactor = 211;
+export const OneMinusConstantColorFactor = 212;
+export const ConstantAlphaFactor = 213;
+export const OneMinusConstantAlphaFactor = 214;
 export const NeverDepth = 0;
 export const AlwaysDepth = 1;
 export const LessDepth = 2;
diff --git a/src/loaders/MaterialLoader.js b/src/loaders/MaterialLoader.js
index aecd7235e57471..d0351ce84b207c 100644
--- a/src/loaders/MaterialLoader.js
+++ b/src/loaders/MaterialLoader.js
@@ -132,6 +132,8 @@ class MaterialLoader extends Loader {
 		if ( json.blendSrcAlpha !== undefined ) material.blendSrcAlpha = json.blendSrcAlpha;
 		if ( json.blendDstAlpha !== undefined ) material.blendDstAlpha = json.blendDstAlpha;
 		if ( json.blendEquationAlpha !== undefined ) material.blendEquationAlpha = json.blendEquationAlpha;
+		if ( json.blendColor !== undefined && material.blendColor !== undefined ) material.blendColor.setHex( json.blendColor );
+		if ( json.blendAlpha !== undefined ) material.blendAlpha = json.blendAlpha;
 		if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask;
 		if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc;
 		if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef;
diff --git a/src/materials/Material.js b/src/materials/Material.js
index c1944dcb9cb6dd..1987edfd69a6ee 100644
--- a/src/materials/Material.js
+++ b/src/materials/Material.js
@@ -1,3 +1,4 @@
+import { Color } from '../math/Color.js';
 import { EventDispatcher } from '../core/EventDispatcher.js';
 import { FrontSide, NormalBlending, LessEqualDepth, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor, AlwaysStencilFunc, KeepStencilOp } from '../constants.js';
 import * as MathUtils from '../math/MathUtils.js';
@@ -33,6 +34,8 @@ class Material extends EventDispatcher {
 		this.blendSrcAlpha = null;
 		this.blendDstAlpha = null;
 		this.blendEquationAlpha = null;
+		this.blendColor = new Color( 0, 0, 0 );
+		this.blendAlpha = 0;
 
 		this.depthFunc = LessEqualDepth;
 		this.depthTest = true;
@@ -331,6 +334,8 @@ class Material extends EventDispatcher {
 		if ( this.blendSrcAlpha !== null ) data.blendSrcAlpha = this.blendSrcAlpha;
 		if ( this.blendDstAlpha !== null ) data.blendDstAlpha = this.blendDstAlpha;
 		if ( this.blendEquationAlpha !== null ) data.blendEquationAlpha = this.blendEquationAlpha;
+		if ( this.blendColor && this.blendColor.isColor ) data.blendColor = this.blendColor.getHex();
+		if ( this.blendAlpha !== 0 ) data.blendAlpha = this.blendAlpha;
 
 		if ( this.depthFunc !== LessEqualDepth ) data.depthFunc = this.depthFunc;
 		if ( this.depthTest === false ) data.depthTest = this.depthTest;
@@ -436,6 +441,8 @@ class Material extends EventDispatcher {
 		this.blendSrcAlpha = source.blendSrcAlpha;
 		this.blendDstAlpha = source.blendDstAlpha;
 		this.blendEquationAlpha = source.blendEquationAlpha;
+		this.blendColor.copy( source.blendColor );
+		this.blendAlpha = source.blendAlpha;
 
 		this.depthFunc = source.depthFunc;
 		this.depthTest = source.depthTest;
diff --git a/src/renderers/webgl/WebGLState.js b/src/renderers/webgl/WebGLState.js
index 486ecd695e00c0..707a9dec629a25 100644
--- a/src/renderers/webgl/WebGLState.js
+++ b/src/renderers/webgl/WebGLState.js
@@ -1,4 +1,5 @@
-import { NotEqualDepth, GreaterDepth, GreaterEqualDepth, EqualDepth, LessEqualDepth, LessDepth, AlwaysDepth, NeverDepth, CullFaceFront, CullFaceBack, CullFaceNone, DoubleSide, BackSide, CustomBlending, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NoBlending, NormalBlending, AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation, ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor, OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor } from '../../constants.js';
+import { NotEqualDepth, GreaterDepth, GreaterEqualDepth, EqualDepth, LessEqualDepth, LessDepth, AlwaysDepth, NeverDepth, CullFaceFront, CullFaceBack, CullFaceNone, DoubleSide, BackSide, CustomBlending, MultiplyBlending, SubtractiveBlending, AdditiveBlending, NoBlending, NormalBlending, AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation, ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor, OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor, ConstantColorFactor, OneMinusConstantColorFactor, ConstantAlphaFactor, OneMinusConstantAlphaFactor } from '../../constants.js';
+import { Color } from '../../math/Color.js';
 import { Vector4 } from '../../math/Vector4.js';
 
 function WebGLState( gl, extensions, capabilities ) {
@@ -325,6 +326,8 @@ function WebGLState( gl, extensions, capabilities ) {
 	let currentBlendEquationAlpha = null;
 	let currentBlendSrcAlpha = null;
 	let currentBlendDstAlpha = null;
+	let currentBlendColor = new Color( 0, 0, 0 );
+	let currentBlendAlpha = 0;
 	let currentPremultipledAlpha = false;
 
 	let currentFlipSided = null;
@@ -600,10 +603,14 @@ function WebGLState( gl, extensions, capabilities ) {
 		[ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR,
 		[ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA,
 		[ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR,
-		[ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA
+		[ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA,
+		[ ConstantColorFactor ]: gl.CONSTANT_COLOR,
+		[ OneMinusConstantColorFactor ]: gl.ONE_MINUS_CONSTANT_COLOR,
+		[ ConstantAlphaFactor ]: gl.CONSTANT_ALPHA,
+		[ OneMinusConstantAlphaFactor ]: gl.ONE_MINUS_CONSTANT_ALPHA
 	};
 
-	function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
+	function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) {
 
 		if ( blending === NoBlending ) {
 
@@ -696,6 +703,8 @@ function WebGLState( gl, extensions, capabilities ) {
 				currentBlendDst = null;
 				currentBlendSrcAlpha = null;
 				currentBlendDstAlpha = null;
+				currentBlendColor.set( 0, 0, 0 );
+				currentBlendAlpha = 0;
 
 				currentBlending = blending;
 				currentPremultipledAlpha = premultipliedAlpha;
@@ -732,6 +741,15 @@ function WebGLState( gl, extensions, capabilities ) {
 
 		}
 
+		if ( blendColor.equals( currentBlendColor ) === false || blendAlpha !== currentBlendAlpha ) {
+
+			gl.blendColor( blendColor.r, blendColor.g, blendColor.b, blendAlpha );
+
+			currentBlendColor.copy( blendColor );
+			currentBlendAlpha = blendAlpha;
+
+		}
+
 		currentBlending = blending;
 		currentPremultipledAlpha = false;
 
@@ -750,7 +768,7 @@ function WebGLState( gl, extensions, capabilities ) {
 
 		( material.blending === NormalBlending && material.transparent === false )
 			? setBlending( NoBlending )
-			: setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
+			: setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha );
 
 		depthBuffer.setFunc( material.depthFunc );
 		depthBuffer.setTest( material.depthTest );
@@ -1174,6 +1192,7 @@ function WebGLState( gl, extensions, capabilities ) {
 		gl.blendEquation( gl.FUNC_ADD );
 		gl.blendFunc( gl.ONE, gl.ZERO );
 		gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO );
+		gl.blendColor( 0, 0, 0, 0 );
 
 		gl.colorMask( true, true, true, true );
 		gl.clearColor( 0, 0, 0, 0 );
@@ -1231,6 +1250,8 @@ function WebGLState( gl, extensions, capabilities ) {
 		currentBlendEquationAlpha = null;
 		currentBlendSrcAlpha = null;
 		currentBlendDstAlpha = null;
+		currentBlendColor = new Color( 0, 0, 0 );
+		currentBlendAlpha = 0;
 		currentPremultipledAlpha = false;
 
 		currentFlipSided = null;


Test patch:
diff --git a/test/unit/src/constants.tests.js b/test/unit/src/constants.tests.js
index 23794c720bfcd6..4752c0fc803d8a 100644
--- a/test/unit/src/constants.tests.js
+++ b/test/unit/src/constants.tests.js
@@ -47,6 +47,10 @@ export default QUnit.module( 'Constants', () => {
 		assert.equal( Constants.DstColorFactor, 208, 'DstColorFactor is equal to 208' );
 		assert.equal( Constants.OneMinusDstColorFactor, 209, 'OneMinusDstColorFactor is equal to 209' );
 		assert.equal( Constants.SrcAlphaSaturateFactor, 210, 'SrcAlphaSaturateFactor is equal to 210' );
+		assert.equal( Constants.ConstantColorFactor, 211, 'ConstantColorFactor is equal to 211' );
+		assert.equal( Constants.OneMinusConstantColorFactor, 212, 'OneMinusConstantColorFactor is equal to 212' );
+		assert.equal( Constants.ConstantAlphaFactor, 213, 'ConstantAlphaFactor is equal to 213' );
+		assert.equal( Constants.OneMinusConstantAlphaFactor, 214, 'OneMinusConstantAlphaFactor is equal to 214' );
 
 		assert.equal( Constants.NeverDepth, 0, 'NeverDepth is equal to 0' );
 		assert.equal( Constants.AlwaysDepth, 1, 'AlwaysDepth is equal to 1' );