From b0ff21aeae381bd2db9a8f675ba19c0cf8cff123 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Sat, 11 Apr 2026 15:06:03 +0200 Subject: [PATCH] Expose premultipliedAlpha on material component Since three.js r178, THREE.MultiplyBlending emits a repeated console warning unless material.premultipliedAlpha is true. Expose premultipliedAlpha as a material schema property and force it on whenever blending is multiply. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/components/material.md | 1 + src/components/material.js | 4 ++++ tests/components/material.test.js | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/docs/components/material.md b/docs/components/material.md index 34e044ee97f..79b030da92b 100644 --- a/docs/components/material.md +++ b/docs/components/material.md @@ -64,6 +64,7 @@ depending on the material type applied. | flatShading | Use `THREE.FlatShading` rather than `THREE.StandardShading`. | false | | offset | Texture offset to be used. | {x: 0, y: 0} | | opacity | Extent of transparency. If the `transparent` property is not `true`, then the material will remain opaque and `opacity` will only affect color. | 1.0 | +| premultipliedAlpha | Whether the material's RGB channels are premultiplied by its alpha channel. Automatically forced to `true` when `blending` is `multiply`. | false | | repeat | Texture repeat to be used. | {x: 1, y: 1} | | magFilter | Which magnifying filter to use when sampling textures. Can be one of `linear` or `nearest`. | `linear` | | minFilter | Which minifying filter to use when sampling textures. Can be one of `linear`, `linear-mipmap-nearest`, `linear-mipmap-linear`, `nearest`, `nearest-mipmap-nearest` or `nearest-mipmap-linear`. | `linear-mipmap-linear` | diff --git a/src/components/material.js b/src/components/material.js index 6b8e15ef5ec..82039d591c3 100644 --- a/src/components/material.js +++ b/src/components/material.js @@ -20,6 +20,7 @@ export var Component = registerComponent('material', { flatShading: {default: false}, offset: {type: 'vec2', default: {x: 0, y: 0}}, opacity: {default: 1.0, min: 0.0, max: 1.0}, + premultipliedAlpha: {default: false}, repeat: {type: 'vec2', default: {x: 1, y: 1}}, magFilter: {default: 'linear', oneOf: ['nearest', 'linear']}, minFilter: { @@ -139,6 +140,9 @@ export var Component = registerComponent('material', { material.vertexColors = data.vertexColorsEnabled; material.visible = data.visible; material.blending = parseBlending(data.blending); + // three.js r178+ requires premultipliedAlpha for MultiplyBlending, + // so force it on regardless of the user-supplied value. + material.premultipliedAlpha = data.blending === 'multiply' ? true : data.premultipliedAlpha; material.dithering = data.dithering; // Check if material needs update. diff --git a/tests/components/material.test.js b/tests/components/material.test.js index d004d4f9fb8..7efd8c5328a 100644 --- a/tests/components/material.test.js +++ b/tests/components/material.test.js @@ -394,6 +394,26 @@ suite('material', function () { el.setAttribute('material', 'blending', 'multiply'); assert.equal(el.components.material.material.blending, THREE.MultiplyBlending); }); + + test('forces premultipliedAlpha when blending is multiply', function () { + el.setAttribute('material', 'blending: multiply; transparent: true'); + assert.strictEqual(el.components.material.material.premultipliedAlpha, true); + }); + + test('forces premultipliedAlpha even if user sets it to false with multiply', function () { + el.setAttribute('material', 'blending: multiply; transparent: true; premultipliedAlpha: false'); + assert.strictEqual(el.components.material.material.premultipliedAlpha, true); + }); + + test('premultipliedAlpha defaults to false for other blending modes', function () { + el.setAttribute('material', 'blending: additive; transparent: true'); + assert.strictEqual(el.components.material.material.premultipliedAlpha, false); + }); + + test('respects user-supplied premultipliedAlpha for non-multiply blending', function () { + el.setAttribute('material', 'blending: additive; transparent: true; premultipliedAlpha: true'); + assert.strictEqual(el.components.material.material.premultipliedAlpha, true); + }); }); suite('anisotropy', function () {