From f62abd6be18cd7a26f067161f4b40e55aaf4dc53 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Sat, 4 Jul 2026 09:38:00 +0200 Subject: [PATCH 01/10] Move shared material helpers to utils/material Extract parseSide, parseBlending, disposeMaterial and the base material property assignment (updateBaseMaterial) from the material component to utils/material.js so they can be reused outside the component. No behavior change. Co-Authored-By: Claude Fable 5 --- src/components/material.js | 83 +-------------------------------- src/utils/material.js | 94 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 81 deletions(-) diff --git a/src/components/material.js b/src/components/material.js index 4ef5889d6ae..9cd6477235c 100644 --- a/src/components/material.js +++ b/src/components/material.js @@ -4,6 +4,7 @@ import { registerComponent } from '../core/component.js'; import { shaders, shaderNames } from '../core/shader.js'; var error = utils.debug('components:material:error'); +var disposeMaterial = utils.material.disposeMaterial; /** * Material component. @@ -130,22 +131,7 @@ export var Component = registerComponent('material', { var oldDataHasKeys; // Base material properties. - material.alphaTest = data.alphaTest; - material.depthTest = data.depthTest !== false; - material.depthWrite = data.depthWrite !== false; - material.opacity = data.opacity; - material.flatShading = data.flatShading; - material.side = parseSide(data.side); - material.transparent = data.transparent !== false || data.opacity < 1.0; - material.vertexColors = data.vertexColorsEnabled; - material.visible = data.visible; - material.blending = parseBlending(data.blending); - // three.js r178+ requires premultipliedAlpha for MultiplyBlending and - // SubtractiveBlending, so force it on regardless of the user-supplied value. - material.premultipliedAlpha = (data.blending === 'multiply' || data.blending === 'subtractive') - ? true - : data.premultipliedAlpha; - material.dithering = data.dithering; + utils.material.updateBaseMaterial(material, data); // Check if material needs update. for (oldDataHasKeys in oldData) { break; } @@ -198,68 +184,3 @@ export var Component = registerComponent('material', { } } }); - -/** - * Return a three.js constant determining which material face sides to render - * based on the side parameter (passed as a component property). - * - * @param {string} [side=front] - `front`, `back`, or `double`. - * @returns {number} THREE.FrontSide, THREE.BackSide, or THREE.DoubleSide. - */ -function parseSide (side) { - switch (side) { - case 'back': { - return THREE.BackSide; - } - case 'double': { - return THREE.DoubleSide; - } - default: { - // Including case `front`. - return THREE.FrontSide; - } - } -} - -/** - * Return a three.js constant determining blending - * - * @param {string} [blending=normal] - `none`, additive`, `subtractive`,`multiply` or `normal`. - * @returns {number} - */ -function parseBlending (blending) { - switch (blending) { - case 'none': { - return THREE.NoBlending; - } - case 'additive': { - return THREE.AdditiveBlending; - } - case 'subtractive': { - return THREE.SubtractiveBlending; - } - case 'multiply': { - return THREE.MultiplyBlending; - } - default: { - return THREE.NormalBlending; - } - } -} - -/** - * Dispose of material from memory and unsubscribe material from scene updates like fog. - */ -function disposeMaterial (material, system) { - material.dispose(); - system.unregisterMaterial(material); - - // Dispose textures on this material - Object.keys(material) - .filter(function (propName) { - return material[propName] && material[propName].isTexture; - }) - .forEach(function (mapName) { - material[mapName].dispose(); - }); -} diff --git a/src/utils/material.js b/src/utils/material.js index 8717d81f4ec..8df63979e12 100644 --- a/src/utils/material.js +++ b/src/utils/material.js @@ -20,6 +20,100 @@ var FILTERING_TYPES = { 'linear-mipmap-linear': THREE.LinearMipmapLinearFilter }; +/** + * Return a three.js constant determining which material face sides to render + * based on the side parameter (passed as a component property). + * + * @param {string} [side=front] - `front`, `back`, or `double`. + * @returns {number} THREE.FrontSide, THREE.BackSide, or THREE.DoubleSide. + */ +export function parseSide (side) { + switch (side) { + case 'back': { + return THREE.BackSide; + } + case 'double': { + return THREE.DoubleSide; + } + default: { + // Including case `front`. + return THREE.FrontSide; + } + } +} + +/** + * Return a three.js constant determining blending + * + * @param {string} [blending=normal] - `none`, additive`, `subtractive`,`multiply` or `normal`. + * @returns {number} + */ +export function parseBlending (blending) { + switch (blending) { + case 'none': { + return THREE.NoBlending; + } + case 'additive': { + return THREE.AdditiveBlending; + } + case 'subtractive': { + return THREE.SubtractiveBlending; + } + case 'multiply': { + return THREE.MultiplyBlending; + } + default: { + return THREE.NormalBlending; + } + } +} + +/** + * Set base material properties shared by all shaders (side, blending, opacity...) + * given data following the base material component schema. + * + * @param {THREE.Material} material - Material to update. + * @param {object} data - Material component (or ) data. + */ +export function updateBaseMaterial (material, data) { + material.alphaTest = data.alphaTest; + material.depthTest = data.depthTest !== false; + material.depthWrite = data.depthWrite !== false; + material.opacity = data.opacity; + material.flatShading = data.flatShading; + material.side = parseSide(data.side); + material.transparent = data.transparent !== false || data.opacity < 1.0; + material.vertexColors = data.vertexColorsEnabled; + material.visible = data.visible; + material.blending = parseBlending(data.blending); + // three.js r178+ requires premultipliedAlpha for MultiplyBlending and + // SubtractiveBlending, so force it on regardless of the user-supplied value. + material.premultipliedAlpha = (data.blending === 'multiply' || data.blending === 'subtractive') + ? true + : data.premultipliedAlpha; + material.dithering = data.dithering; +} + +/** + * Dispose of material from memory and unsubscribe material from scene updates like fog. + * + * @param {THREE.Material} material - Material to dispose. + * @param {object} system - Material system. + */ +export function disposeMaterial (material, system) { + material.dispose(); + system.unregisterMaterial(material); + + // Dispose textures on this material + Object.keys(material) + .filter(function (propName) { + return material[propName] && material[propName].isTexture; + }) + .forEach(function (mapName) { + material[mapName].dispose(); + }); +} + /** * Set texture properties such as repeat and offset. * From dc33144b4fe8a0bf65d1cbde46ae8b8676846cec Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Sat, 4 Jul 2026 09:38:42 +0200 Subject: [PATCH 02/10] Add asset element and material property type (fixes #5457) Materials can now be defined once in and shared across entities: - creates a THREE.Material through the registered shaders (standard, flat, custom) during scene loading, including textures, and blocks rendering until it is ready. - New `material` property type resolves an ID selector to the shared THREE.Material so any component can accept a material in its schema. - The material component accepts a `material` property; when set, the shared material is used as-is and the component does not own (dispose/register) it. - Attribute changes on update the shared material live for every entity using it; removing the element disposes it. Co-Authored-By: Claude Fable 5 --- docs/components/material.md | 14 ++ docs/core/asset-management-system.md | 1 + docs/core/component.md | 1 + docs/primitives/a-material.md | 96 +++++++++++ examples/test/material-asset/index.html | 37 ++++ examples/test/material-asset/perf.html | 139 ++++++++++++++++ src/components/material.js | 30 +++- src/core/a-assets.js | 7 +- src/core/a-material.js | 213 ++++++++++++++++++++++++ src/core/a-node.js | 1 + src/core/propertyTypes.js | 44 +++++ src/index.js | 1 + tests/core/a-material.test.js | 119 +++++++++++++ tests/core/propertyTypes.test.js | 27 +++ 14 files changed, 724 insertions(+), 6 deletions(-) create mode 100644 docs/primitives/a-material.md create mode 100644 examples/test/material-asset/index.html create mode 100644 examples/test/material-asset/perf.html create mode 100644 src/core/a-material.js create mode 100644 tests/core/a-material.test.js diff --git a/docs/components/material.md b/docs/components/material.md index 95baf580cb0..79dd78eca6d 100644 --- a/docs/components/material.md +++ b/docs/components/material.md @@ -15,6 +15,7 @@ examples: src: https://glitch.com/edit/#!/aframe-displacement-offset-registershader?path=public/index.html --- +[amaterial]: ../primitives/a-material.md [fog]: ./fog.md [geometry]: ./geometry.md @@ -48,6 +49,18 @@ Here is an example of using an example custom material: material="shader: ocean; color: blue; wave-height: 10"> ``` +Here is an example of sharing one material instance across entities using a +[`` asset][amaterial]: + +```html + + + + + + +``` + ## Properties [flat]: #flat @@ -62,6 +75,7 @@ depending on the material type applied. | alphaTest | Alpha test threshold for transparency. | 0 | | depthTest | Whether depth testing is enabled when rendering the material. | true | | flatShading | Use `THREE.FlatShading` rather than `THREE.StandardShading`. | false | +| material | Selector to a [`` asset][amaterial] to use as shared material (e.g., `#wood`). When set, all other properties are ignored and the material is entirely defined by the asset. | None | | 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` or `subtractive`. | false | diff --git a/docs/core/asset-management-system.md b/docs/core/asset-management-system.md index 6ae7f1b6d90..8fb318ddc92 100644 --- a/docs/core/asset-management-system.md +++ b/docs/core/asset-management-system.md @@ -22,6 +22,7 @@ We place assets within ``, and we place `` within ``. Assets include: - `` - Miscellaneous assets such as 3D models and materials +- `` - Shared materials, created (with their textures) at load time - `