From 0534c393ff74acf37d8dde3f67b2399063c6ae34 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 3 Mar 2026 10:18:33 +0100 Subject: [PATCH 1/2] OutlineNode: Support sprites. (#33112) --- examples/jsm/tsl/display/OutlineNode.js | 40 ++++++++++++++++--------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/examples/jsm/tsl/display/OutlineNode.js b/examples/jsm/tsl/display/OutlineNode.js index a4f5512ac82379..8fc0d015c0072b 100644 --- a/examples/jsm/tsl/display/OutlineNode.js +++ b/examples/jsm/tsl/display/OutlineNode.js @@ -1,5 +1,5 @@ -import { DepthTexture, FloatType, RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, RendererUtils, NodeUpdateType } from 'three/webgpu'; -import { Loop, int, exp, min, float, mul, uv, vec2, vec3, Fn, textureSize, orthographicDepthToViewZ, screenUV, nodeObject, uniform, vec4, passTexture, texture, perspectiveDepthToViewZ, positionView, reference } from 'three/tsl'; +import { DepthTexture, FloatType, RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, SpriteNodeMaterial, RendererUtils, NodeUpdateType } from 'three/webgpu'; +import { Loop, int, exp, min, float, mul, uv, vec2, vec3, Fn, textureSize, orthographicDepthToViewZ, screenUV, nodeObject, uniform, vec4, passTexture, texture, perspectiveDepthToViewZ, positionView, reference, color } from 'three/tsl'; const _quadMesh = /*@__PURE__*/ new QuadMesh(); const _size = /*@__PURE__*/ new Vector2(); @@ -293,9 +293,13 @@ class OutlineNode extends TempNode { * @type {NodeMaterial} */ this._depthMaterial = new NodeMaterial(); - this._depthMaterial.fragmentNode = vec4( 0, 0, 0, 1 ); + this._depthMaterial.colorNode = color( 0, 0, 0 ); this._depthMaterial.name = 'OutlineNode.depth'; + this._depthSpriteMaterial = new SpriteNodeMaterial(); + this._depthSpriteMaterial.colorNode = color( 0, 0, 0 ); + this._depthSpriteMaterial.name = 'OutlineNode.depthSprite'; + /** * The material for preparing the mask. * @@ -305,6 +309,9 @@ class OutlineNode extends TempNode { this._prepareMaskMaterial = new NodeMaterial(); this._prepareMaskMaterial.name = 'OutlineNode.prepareMask'; + this._prepareMaskSpriteMaterial = new SpriteNodeMaterial(); + this._prepareMaskSpriteMaterial.name = 'OutlineNode.prepareMaskSprite'; + /** * The copy material * @@ -459,14 +466,14 @@ class OutlineNode extends TempNode { // 1. Draw non-selected objects in the depth buffer - scene.overrideMaterial = this._depthMaterial; - renderer.setRenderTarget( this._renderTargetDepthBuffer ); - renderer.setRenderObjectFunction( ( object, ...params ) => { + renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => { if ( this._selectionCache.has( object ) === false ) { - renderer.renderObject( object, ...params ); + const overrideMaterial = object.isSprite ? this._depthSpriteMaterial : this._depthMaterial; + + renderer.renderObject( object, scene, camera, geometry, overrideMaterial, group, lightsNode, clippingContext ); } @@ -477,14 +484,14 @@ class OutlineNode extends TempNode { // 2. Draw only the selected objects by comparing the depth buffer of non-selected objects - scene.overrideMaterial = this._prepareMaskMaterial; - renderer.setRenderTarget( this._renderTargetMaskBuffer ); - renderer.setRenderObjectFunction( ( object, ...params ) => { + renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => { if ( this._selectionCache.has( object ) === true ) { - renderer.renderObject( object, ...params ); + const overrideMaterial = object.isSprite ? this._prepareMaskSpriteMaterial : this._prepareMaskMaterial; + + renderer.renderObject( object, scene, camera, geometry, overrideMaterial, group, lightsNode, clippingContext ); } @@ -587,13 +594,16 @@ class OutlineNode extends TempNode { } const depthTest = positionView.z.lessThanEqual( viewZNode ).select( 1, 0 ); - return vec4( 0.0, depthTest, 1.0, 1.0 ); + return vec3( 0.0, depthTest, 1.0 ); }; - this._prepareMaskMaterial.fragmentNode = prepareMask(); + this._prepareMaskMaterial.colorNode = prepareMask(); this._prepareMaskMaterial.needsUpdate = true; + this._prepareMaskSpriteMaterial.colorNode = prepareMask(); + this._prepareMaskSpriteMaterial.needsUpdate = true; + // copy material this._materialCopy.fragmentNode = this._maskTextureUniform; @@ -712,7 +722,9 @@ class OutlineNode extends TempNode { this._renderTargetComposite.dispose(); this._depthMaterial.dispose(); + this._depthSpriteMaterial.dispose(); this._prepareMaskMaterial.dispose(); + this._prepareMaskSpriteMaterial.dispose(); this._materialCopy.dispose(); this._edgeDetectionMaterial.dispose(); this._separableBlurMaterial.dispose(); @@ -733,7 +745,7 @@ class OutlineNode extends TempNode { const selectedObject = this.selectedObjects[ i ]; selectedObject.traverse( ( object ) => { - if ( object.isMesh ) this._selectionCache.add( object ); + if ( object.isMesh || object.isSprite ) this._selectionCache.add( object ); } ); From 051fbfd1a9a2784db78d6439442bfab00edfecc0 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 3 Mar 2026 10:51:20 +0100 Subject: [PATCH 2/2] NodeMaterialObserver: Avoid usage of `Object.keys()`. (#33113) --- .../nodes/manager/NodeMaterialObserver.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/materials/nodes/manager/NodeMaterialObserver.js b/src/materials/nodes/manager/NodeMaterialObserver.js index bdb0d242a8e824..4441a083264e22 100644 --- a/src/materials/nodes/manager/NodeMaterialObserver.js +++ b/src/materials/nodes/manager/NodeMaterialObserver.js @@ -393,9 +393,6 @@ class NodeMaterialObserver { const attributes = geometry.attributes; const storedAttributes = storedGeometryData.attributes; - const storedAttributeNames = Object.keys( storedAttributes ); - const currentAttributeNames = Object.keys( attributes ); - if ( storedGeometryData.id !== geometry.id ) { storedGeometryData.id = geometry.id; @@ -403,16 +400,16 @@ class NodeMaterialObserver { } - if ( storedAttributeNames.length !== currentAttributeNames.length ) { + // attributes - renderObjectData.geometry.attributes = this.getAttributesData( attributes ); - return false; + let currentAttributeCount = 0; + let storedAttributeCount = 0; - } + for ( const _ in attributes ) currentAttributeCount ++; // eslint-disable-line no-unused-vars - // compare each attribute + for ( const name in storedAttributes ) { - for ( const name of storedAttributeNames ) { + storedAttributeCount ++; const storedAttributeData = storedAttributes[ name ]; const attribute = attributes[ name ]; @@ -435,6 +432,13 @@ class NodeMaterialObserver { } + if ( storedAttributeCount !== currentAttributeCount ) { + + renderObjectData.geometry.attributes = this.getAttributesData( attributes ); + return false; + + } + // check index const index = geometry.index;