Parcourir la source

TSL: Migrate vertex accessor classes to TSL Fn (#33674)

sunag il y a 1 mois
Parent
commit
7ad644be80

BIN
examples/screenshots/webgpu_postprocessing_motion_blur.jpg


+ 9 - 13
src/materials/nodes/NodeMaterial.js

@@ -1,16 +1,16 @@
 import { Material } from '../Material.js';
 
 import { hashArray, hashString } from '../../nodes/core/NodeUtils.js';
-import { output, diffuseColor, emissive, varyingProperty } from '../../nodes/core/PropertyNode.js';
+import { output, diffuseColor, emissive } from '../../nodes/core/PropertyNode.js';
 import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal, materialLightMap, materialAO } from '../../nodes/accessors/MaterialNode.js';
 import { modelViewProjection } from '../../nodes/accessors/ModelViewProjectionNode.js';
 import { normalLocal } from '../../nodes/accessors/Normal.js';
-import { instancedMesh } from '../../nodes/accessors/InstancedMeshNode.js';
-import { batch } from '../../nodes/accessors/BatchNode.js';
+import { instancedMesh, instanceColor } from '../../nodes/accessors/Instance.js';
+import { batch, batchColor } from '../../nodes/accessors/Batch.js';
 import { materialReference } from '../../nodes/accessors/MaterialReferenceNode.js';
 import { positionLocal, positionView } from '../../nodes/accessors/Position.js';
-import { skinning } from '../../nodes/accessors/SkinningNode.js';
-import { morphReference } from '../../nodes/accessors/MorphNode.js';
+import { skinning } from '../../nodes/accessors/Skinning.js';
+import { morphReference } from '../../nodes/accessors/Morph.js';
 import { fwidth, mix, smoothstep } from '../../nodes/math/MathNode.js';
 import { float, vec3, vec4, bool } from '../../nodes/tsl/TSLBase.js';
 import AONode from '../../nodes/lighting/AONode.js';
@@ -766,13 +766,13 @@ class NodeMaterial extends Material {
 
 		if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) {
 
-			morphReference( object ).toStack();
+			morphReference( object );
 
 		}
 
 		if ( object.isSkinnedMesh === true ) {
 
-			skinning( object ).toStack();
+			skinning( object );
 
 		}
 
@@ -788,13 +788,13 @@ class NodeMaterial extends Material {
 
 		if ( object.isBatchedMesh ) {
 
-			batch( object ).toStack();
+			batch( object );
 
 		}
 
 		if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) {
 
-			instancedMesh( object ).toStack();
+			instancedMesh( object );
 
 		}
 
@@ -844,16 +844,12 @@ class NodeMaterial extends Material {
 
 		if ( object.instanceColor ) {
 
-			const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' );
-
 			colorNode = instanceColor.mul( colorNode );
 
 		}
 
 		if ( object.isBatchedMesh && object._colorsTexture ) {
 
-			const batchColor = varyingProperty( 'vec3', 'vBatchColor' );
-
 			colorNode = batchColor.mul( colorNode );
 
 		}

+ 0 - 5
src/nodes/Nodes.js

@@ -43,24 +43,19 @@ import * as NodeUtils from './core/NodeUtils.js';
 export { NodeUtils };
 
 // accessors
-export { default as BatchNode } from './accessors/BatchNode.js';
 export { default as BufferAttributeNode } from './accessors/BufferAttributeNode.js';
 export { default as BufferNode } from './accessors/BufferNode.js';
 export { default as BuiltinNode } from './accessors/BuiltinNode.js';
 export { default as ClippingNode } from './accessors/ClippingNode.js';
 export { default as CubeTextureNode } from './accessors/CubeTextureNode.js';
-export { default as InstanceNode } from './accessors/InstanceNode.js';
-export { default as InstancedMeshNode } from './accessors/InstancedMeshNode.js';
 export { default as MaterialNode } from './accessors/MaterialNode.js';
 export { default as MaterialReferenceNode } from './accessors/MaterialReferenceNode.js';
 export { default as ModelNode } from './accessors/ModelNode.js';
-export { default as MorphNode } from './accessors/MorphNode.js';
 export { default as Object3DNode } from './accessors/Object3DNode.js';
 export { default as PointUVNode } from './accessors/PointUVNode.js';
 export { default as ReferenceBaseNode } from './accessors/ReferenceBaseNode.js';
 export { default as ReferenceNode } from './accessors/ReferenceNode.js';
 export { default as RendererReferenceNode } from './accessors/RendererReferenceNode.js';
-export { default as SkinningNode } from './accessors/SkinningNode.js';
 export { default as StorageBufferNode } from './accessors/StorageBufferNode.js';
 export { default as StorageTexture3DNode } from './accessors/StorageTexture3DNode.js';
 export { default as StorageTextureNode } from './accessors/StorageTextureNode.js';

+ 4 - 5
src/nodes/TSL.js

@@ -63,14 +63,13 @@ export * from './accessors/BuiltinNode.js';
 export * from './accessors/Camera.js';
 export * from './accessors/VertexColorNode.js';
 export * from './accessors/CubeTextureNode.js';
-export * from './accessors/InstanceNode.js';
-export * from './accessors/InstancedMeshNode.js';
-export * from './accessors/BatchNode.js';
+export * from './accessors/Instance.js';
+export * from './accessors/Batch.js';
 export * from './accessors/MaterialNode.js';
 export * from './accessors/MaterialProperties.js';
 export * from './accessors/MaterialReferenceNode.js';
 export * from './accessors/RendererReferenceNode.js';
-export * from './accessors/MorphNode.js';
+export * from './accessors/Morph.js';
 export * from './accessors/TextureBicubic.js';
 export * from './accessors/ModelNode.js';
 export * from './accessors/ModelViewProjectionNode.js';
@@ -80,7 +79,7 @@ export * from './accessors/PointUVNode.js';
 export * from './accessors/Position.js';
 export * from './accessors/ReferenceNode.js';
 export * from './accessors/ReflectVector.js';
-export * from './accessors/SkinningNode.js';
+export * from './accessors/Skinning.js';
 export * from './accessors/SceneProperties.js';
 export * from './accessors/StorageBufferNode.js';
 export * from './accessors/StorageTexture3DNode.js';

+ 108 - 0
src/nodes/accessors/Batch.js

@@ -0,0 +1,108 @@
+
+import { normalLocal } from './Normal.js';
+import { positionLocal } from './Position.js';
+import { vec3, mat3, mat4, int, ivec2, float, Fn } from '../tsl/TSLBase.js';
+import { textureLoad } from './TextureNode.js';
+import { textureSize } from './TextureSizeNode.js';
+import { tangentLocal } from './Tangent.js';
+import { instanceIndex, drawIndex } from '../core/IndexNode.js';
+import { varyingProperty } from '../core/PropertyNode.js';
+
+/**
+ * TSL function that retrieves the batching color for a given instance ID from a colors texture.
+ *
+ * @param {Node<texture>} colorsTexture - The colors texture.
+ * @param {Node<int>} id - The instance or batch ID.
+ * @returns {Node<vec3>} The retrieved color.
+ */
+const getBatchingColor = /*@__PURE__*/ Fn( ( [ colorsTexture, id ] ) => {
+
+	const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ).toConst();
+	const j = int( id );
+	const x = j.mod( size ).toConst();
+	const y = j.div( size ).toConst();
+	return textureLoad( colorsTexture, ivec2( x, y ) ).rgb;
+
+} );
+
+/**
+ * TSL function that retrieves the indirect index for a given batch ID.
+ *
+ * @param {BatchedMesh} batchMesh - The batched mesh.
+ * @param {Node<int>} id - The draw or instance ID.
+ * @returns {Node<uint>} The indirect index.
+ */
+const getIndirectIndex = /*@__PURE__*/ Fn( ( [ indirectTexture, id ] ) => {
+
+	const size = int( textureSize( textureLoad( indirectTexture ), 0 ).x ).toConst();
+	const x = int( id ).mod( size ).toConst();
+	const y = int( id ).div( size ).toConst();
+	return textureLoad( indirectTexture, ivec2( x, y ) ).x;
+
+} );
+
+/**
+ * TSL object representing a varying property for the batching color vector.
+ *
+ * @type {VaryingNode<vec3>}
+ */
+export const batchColor = /*@__PURE__*/ varyingProperty( 'vec3', 'vBatchColor' );
+
+/**
+ * TSL function representing the vertex shader batching setup.
+ * Applies the batch transformation matrix to positionLocal, normalLocal, and tangentLocal.
+ * Also assigns the batch color if a color texture is present.
+ *
+ * @tsl
+ * @function
+ * @param {BatchedMesh} batchMesh - The batched mesh.
+ */
+export const batch = /*@__PURE__*/ Fn( ( [ batchMesh ], builder ) => {
+
+	const batchingIdNode = builder.getDrawIndex() === null ? instanceIndex : drawIndex;
+
+	const indirectId = getIndirectIndex( batchMesh._indirectTexture, int( batchingIdNode ) );
+
+	const matricesTexture = batchMesh._matricesTexture;
+
+	const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst();
+	const j = float( indirectId ).mul( 4 ).toInt().toConst();
+
+	const x = j.mod( size ).toConst();
+	const y = j.div( size ).toConst();
+	const batchingMatrix = mat4(
+		textureLoad( matricesTexture, ivec2( x, y ) ),
+		textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ),
+		textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ),
+		textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) )
+	);
+
+	const colorsTexture = batchMesh._colorsTexture;
+
+	if ( colorsTexture !== null ) {
+
+		const color = getBatchingColor( colorsTexture, indirectId );
+
+		batchColor.assign( color );
+
+	}
+
+	const bm = mat3( batchingMatrix );
+
+	positionLocal.assign( batchingMatrix.mul( positionLocal ) );
+
+	const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) );
+
+	const batchingNormal = bm.mul( transformedNormal ).xyz;
+
+	normalLocal.assign( batchingNormal );
+
+	if ( builder.hasGeometryAttribute( 'tangent' ) ) {
+
+		tangentLocal.mulAssign( bm );
+
+	}
+
+}, 'void' );
+
+

+ 0 - 163
src/nodes/accessors/BatchNode.js

@@ -1,163 +0,0 @@
-import Node from '../core/Node.js';
-import { normalLocal } from './Normal.js';
-import { positionLocal } from './Position.js';
-import { nodeProxy, vec3, mat3, mat4, int, ivec2, float, Fn } from '../tsl/TSLBase.js';
-import { textureLoad } from './TextureNode.js';
-import { textureSize } from './TextureSizeNode.js';
-import { tangentLocal } from './Tangent.js';
-import { instanceIndex, drawIndex } from '../core/IndexNode.js';
-import { varyingProperty } from '../core/PropertyNode.js';
-
-/**
- * This node implements the vertex shader logic which is required
- * when rendering 3D objects via batching. `BatchNode` must be used
- * with instances of {@link BatchedMesh}.
- *
- * @augments Node
- */
-class BatchNode extends Node {
-
-	static get type() {
-
-		return 'BatchNode';
-
-	}
-
-	/**
-	 * Constructs a new batch node.
-	 *
-	 * @param {BatchedMesh} batchMesh - A reference to batched mesh.
-	 */
-	constructor( batchMesh ) {
-
-		super( 'void' );
-
-		/**
-		 * A reference to batched mesh.
-		 *
-		 * @type {BatchedMesh}
-		 */
-		this.batchMesh = batchMesh;
-
-		/**
-		 * The batching index node.
-		 *
-		 * @type {?IndexNode}
-		 * @default null
-		 */
-		this.batchingIdNode = null;
-
-	}
-
-	/**
-	 * Setups the internal buffers and nodes and assigns the transformed vertex data
-	 * to predefined node variables for accumulation. That follows the same patterns
-	 * like with morph and skinning nodes.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 */
-	setup( builder ) {
-
-		if ( this.batchingIdNode === null ) {
-
-			if ( builder.getDrawIndex() === null ) {
-
-				this.batchingIdNode = instanceIndex;
-
-			} else {
-
-				this.batchingIdNode = drawIndex;
-
-			}
-
-		}
-
-		const getIndirectIndex = Fn( ( [ id ] ) => {
-
-			const size = int( textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 ).x ).toConst();
-			const x = int( id ).mod( size ).toConst();
-			const y = int( id ).div( size ).toConst();
-			return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x;
-
-		} ).setLayout( {
-			name: 'getIndirectIndex',
-			type: 'uint',
-			inputs: [
-				{ name: 'id', type: 'int' }
-			]
-		} );
-
-		const indirectId = getIndirectIndex( int( this.batchingIdNode ) );
-
-		const matricesTexture = this.batchMesh._matricesTexture;
-
-		const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ).toConst();
-		const j = float( indirectId ).mul( 4 ).toInt().toConst();
-
-		const x = j.mod( size ).toConst();
-		const y = j.div( size ).toConst();
-		const batchingMatrix = mat4(
-			textureLoad( matricesTexture, ivec2( x, y ) ),
-			textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ),
-			textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ),
-			textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) )
-		);
-
-
-		const colorsTexture = this.batchMesh._colorsTexture;
-
-		if ( colorsTexture !== null ) {
-
-			const getBatchingColor = Fn( ( [ id ] ) => {
-
-				const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ).toConst();
-				const j = id;
-				const x = j.mod( size ).toConst();
-				const y = j.div( size ).toConst();
-				return textureLoad( colorsTexture, ivec2( x, y ) ).rgb;
-
-			} ).setLayout( {
-				name: 'getBatchingColor',
-				type: 'vec3',
-				inputs: [
-					{ name: 'id', type: 'int' }
-				]
-			} );
-
-			const color = getBatchingColor( indirectId );
-
-			varyingProperty( 'vec3', 'vBatchColor' ).assign( color );
-
-		}
-
-		const bm = mat3( batchingMatrix );
-
-		positionLocal.assign( batchingMatrix.mul( positionLocal ) );
-
-		const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) );
-
-		const batchingNormal = bm.mul( transformedNormal ).xyz;
-
-		normalLocal.assign( batchingNormal );
-
-		if ( builder.hasGeometryAttribute( 'tangent' ) ) {
-
-			tangentLocal.mulAssign( bm );
-
-		}
-
-	}
-
-}
-
-export default BatchNode;
-
-/**
- * TSL function for creating a batch node.
- *
- * @tsl
- * @function
- * @param {BatchedMesh} batchMesh - A reference to batched mesh.
- * @returns {BatchNode}
- */
-export const batch = /*@__PURE__*/ nodeProxy( BatchNode ).setParameterLength( 1 );

+ 271 - 0
src/nodes/accessors/Instance.js

@@ -0,0 +1,271 @@
+
+import { vec3, mat4, Fn } from '../tsl/TSLBase.js';
+import { OnFrameUpdate, OnObjectUpdate } from '../utils/EventNode.js';
+import { normalLocal, transformNormal } from './Normal.js';
+import { positionLocal, positionPrevious } from './Position.js';
+import { varyingProperty } from '../core/PropertyNode.js';
+import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js';
+import { buffer } from './BufferNode.js';
+import { storage } from './StorageBufferNode.js';
+import { instanceIndex } from '../core/IndexNode.js';
+
+import { InstancedInterleavedBuffer } from '../../core/InstancedInterleavedBuffer.js';
+import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js';
+import { DynamicDrawUsage } from '../../constants.js';
+
+const _matrixBuffers = /*@__PURE__*/ new WeakMap();
+const _colorBuffers = /*@__PURE__*/ new WeakMap();
+const _previousInstanceMatrices = /*@__PURE__*/ new WeakMap();
+
+/**
+ * Creates the appropriate node for instanced matrix transformations.
+ * Depending on buffer limits and storage capability, returns either a storage, buffer, or instanced interleaved attribute node.
+ *
+ * @param {NodeBuilder} builder - The current node builder.
+ * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - The matrix buffer attribute.
+ * @param {number} count - The instance count.
+ * @returns {Node} The matrix node.
+ */
+function createInstanceMatrixNode( builder, instanceMatrix, count ) {
+
+	let instanceMatrixNode;
+
+	const isStorageMatrix = instanceMatrix.isStorageInstancedBufferAttribute === true;
+
+	if ( isStorageMatrix ) {
+
+		instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
+
+	} else {
+
+		const uniformBufferSize = count * 16 * 4;
+
+		if ( uniformBufferSize <= builder.getUniformBufferLimit() ) {
+
+			instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
+
+		} else {
+
+			let interleaved = _matrixBuffers.get( instanceMatrix );
+
+			if ( ! interleaved ) {
+
+				interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 );
+				_matrixBuffers.set( instanceMatrix, interleaved );
+
+			}
+
+			const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
+
+			const instanceBuffers = [
+				bufferFn( interleaved, 'vec4', 16, 0 ),
+				bufferFn( interleaved, 'vec4', 16, 4 ),
+				bufferFn( interleaved, 'vec4', 16, 8 ),
+				bufferFn( interleaved, 'vec4', 16, 12 )
+			];
+
+			instanceMatrixNode = mat4( ...instanceBuffers );
+
+		}
+
+	}
+
+	return instanceMatrixNode;
+
+}
+
+/**
+ * Retrieves or initializes the previous frame instance matrix node for motion vectors.
+ * Uses a WeakMap to cache previous frame instance matrices and their TSL nodes.
+ *
+ * @param {InstancedMesh} instancedMesh - The instanced mesh object.
+ * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - The current matrix buffer attribute.
+ * @param {NodeBuilder} builder - The current node builder.
+ * @param {number} count - The instance count.
+ * @returns {Node} The previous frame instance matrix node.
+ */
+function getPreviousInstance( instancedMesh, instanceMatrix, builder, count ) {
+
+	let data = _previousInstanceMatrices.get( instancedMesh );
+
+	if ( data === undefined ) {
+
+		const previousInstanceMatrix = instanceMatrix.clone();
+
+		data = {
+			previousInstanceMatrix,
+			node: createInstanceMatrixNode( builder, previousInstanceMatrix, count )
+		};
+
+		_previousInstanceMatrices.set( instancedMesh, data );
+
+	}
+
+	return data.node;
+
+}
+
+/**
+ * TSL object representing a varying property for the instanced color vector.
+ *
+ * @type {VaryingNode<vec3>}
+ */
+export const instanceColor = /*@__PURE__*/ varyingProperty( 'vec3', 'vInstanceColor' );
+
+/**
+ * TSL function representing the standard instancing vertex shader setup.
+ * Transforms positionLocal and normalLocal, and assigns varying color in-place.
+ *
+ * @tsl
+ * @function
+ * @param {number} count - The instance count.
+ * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} matrices - The instanced transformation matrices.
+ * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} [colors=null] - The optional instanced colors.
+ */
+export const instance = /*@__PURE__*/ Fn( ( [ count, matrices, colors = null ], builder ) => {
+
+	// get numeric value (non-node)
+	count = count.value;
+
+	const isStorageMatrix = matrices.isStorageInstancedBufferAttribute === true;
+	const isStorageColor = colors && colors.isStorageInstancedBufferAttribute === true;
+
+	const instanceMatrixNode = createInstanceMatrixNode( builder, matrices, count );
+
+	// interleaved buffer tracking for matrix
+	let interleavedMatrix = null;
+
+	if ( ! isStorageMatrix ) {
+
+		const uniformBufferSize = count * 16 * 4;
+
+		if ( uniformBufferSize > builder.getUniformBufferLimit() ) {
+
+			interleavedMatrix = _matrixBuffers.get( matrices );
+
+		}
+
+	}
+
+	let instanceColorNode = null;
+	let interleavedColor = null;
+
+	if ( colors ) {
+
+		if ( isStorageColor ) {
+
+			instanceColorNode = storage( colors, 'vec3', Math.max( colors.count, 1 ) ).element( instanceIndex );
+
+		} else {
+
+			let bufferAttribute = _colorBuffers.get( colors );
+
+			if ( ! bufferAttribute ) {
+
+				bufferAttribute = new InstancedBufferAttribute( colors.array, 3 );
+				_colorBuffers.set( colors, bufferAttribute );
+
+			}
+
+			interleavedColor = bufferAttribute;
+
+			const bufferFn = colors.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
+
+			instanceColorNode = vec3( bufferFn( bufferAttribute, 'vec3', 3, 0 ) );
+
+		}
+
+	}
+
+	// Synchronization of dynamic buffer updates per frame
+	if ( interleavedMatrix !== null || interleavedColor !== null ) {
+
+		OnFrameUpdate( () => {
+
+			if ( interleavedMatrix !== null ) {
+
+				interleavedMatrix.clearUpdateRanges();
+				interleavedMatrix.updateRanges.push( ...matrices.updateRanges );
+
+				if ( matrices.version !== interleavedMatrix.version ) {
+
+					interleavedMatrix.version = matrices.version;
+
+				}
+
+			}
+
+			if ( colors && interleavedColor !== null ) {
+
+				interleavedColor.clearUpdateRanges();
+				interleavedColor.updateRanges.push( ...colors.updateRanges );
+
+				if ( colors.version !== interleavedColor.version ) {
+
+					interleavedColor.version = colors.version;
+
+				}
+
+			}
+
+		} );
+
+	}
+
+	// POSITION
+
+	const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
+	positionLocal.assign( instancePosition );
+
+	if ( builder.needsPreviousData() ) {
+
+		const instancedMesh = builder.object;
+
+		OnObjectUpdate( ( { object } ) => {
+
+			const previousInstanceData = _previousInstanceMatrices.get( object );
+
+			previousInstanceData.previousInstanceMatrix.array.set( matrices.array );
+
+		} );
+
+		const previousInstanceMatrixNode = getPreviousInstance( instancedMesh, matrices, builder, count );
+		positionPrevious.assign( previousInstanceMatrixNode.mul( positionPrevious ).xyz );
+
+	}
+
+	// NORMAL
+
+	if ( builder.hasGeometryAttribute( 'normal' ) ) {
+
+		const instanceNormal = transformNormal( normalLocal, instanceMatrixNode );
+		normalLocal.assign( instanceNormal );
+
+	}
+
+	// COLOR
+
+	if ( instanceColorNode !== null ) {
+
+		instanceColor.assign( instanceColorNode );
+
+	}
+
+}, 'void' );
+
+/**
+ * TSL wrapper for applying instanced mesh rendering setup.
+ *
+ * @tsl
+ * @function
+ * @param {InstancedMesh} instancedMesh - The instanced mesh.
+ */
+export const instancedMesh = /*@__PURE__*/ Fn( ( [ instancedMesh ] ) => {
+
+	const { count, instanceMatrix, instanceColor } = instancedMesh;
+
+	instance( count, instanceMatrix, instanceColor );
+
+}, 'void' );
+
+

+ 0 - 367
src/nodes/accessors/InstanceNode.js

@@ -1,367 +0,0 @@
-import Node from '../core/Node.js';
-import { varyingProperty } from '../core/PropertyNode.js';
-import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js';
-import { normalLocal, transformNormal } from './Normal.js';
-import { positionLocal, positionPrevious } from './Position.js';
-import { nodeProxy, vec3, mat4 } from '../tsl/TSLBase.js';
-import { NodeUpdateType } from '../core/constants.js';
-import { buffer } from '../accessors/BufferNode.js';
-import { storage } from './StorageBufferNode.js';
-import { instanceIndex } from '../core/IndexNode.js';
-
-import { InstancedInterleavedBuffer } from '../../core/InstancedInterleavedBuffer.js';
-import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js';
-import { DynamicDrawUsage } from '../../constants.js';
-
-/**
- * This node implements the vertex shader logic which is required
- * when rendering 3D objects via instancing. The code makes sure
- * vertex positions, normals and colors can be modified via instanced
- * data.
- *
- * @augments Node
- */
-class InstanceNode extends Node {
-
-	static get type() {
-
-		return 'InstanceNode';
-
-	}
-
-	/**
-	 * Constructs a new instance node.
-	 *
-	 * @param {number} count - The number of instances.
-	 * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
-	 * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
-	 */
-	constructor( count, instanceMatrix, instanceColor = null ) {
-
-		super( 'void' );
-
-		/**
-		 * The number of instances.
-		 *
-		 * @type {number}
-		 */
-		this.count = count;
-
-		/**
-		 * Instanced buffer attribute representing the transformation of instances.
-		 *
-		 * @type {InstancedBufferAttribute}
-		 */
-		this.instanceMatrix = instanceMatrix;
-
-		/**
-		 * Instanced buffer attribute representing the color of instances.
-		 *
-		 * @type {InstancedBufferAttribute}
-		 */
-		this.instanceColor = instanceColor;
-
-		/**
-		 * The node that represents the instance matrix data.
-		 *
-		 * @type {?Node}
-		 */
-		this.instanceMatrixNode = null;
-
-		/**
-		 * The node that represents the instance color data.
-		 *
-		 * @type {?Node}
-		 * @default null
-		 */
-		this.instanceColorNode = null;
-
-		/**
-		 * The update type is set to `frame` for updating
-		 * velocity-related data.
-		 *
-		 * @type {string}
-		 * @default 'frame'
-		 */
-		this.updateType = NodeUpdateType.FRAME;
-
-		/**
-		 * The update type is set to `frame` since an update
-		 * of instanced buffer data must be checked per frame.
-		 *
-		 * @type {string}
-		 * @default 'frame'
-		 */
-		this.updateBeforeType = NodeUpdateType.FRAME;
-
-		/**
-		 * A reference to a buffer that is used by `instanceMatrixNode`.
-		 *
-		 * @type {?InstancedInterleavedBuffer}
-		 */
-		this.buffer = null;
-
-		/**
-		 * A reference to a buffer that is used by `instanceColorNode`.
-		 *
-		 * @type {?InstancedBufferAttribute}
-		 */
-		this.bufferColor = null;
-
-		/**
-		 * The previous instance matrices. Required for computing motion vectors.
-		 *
-		 * @type {?Node}
-		 * @default null
-		 */
-		this.previousInstanceMatrixNode = null;
-
-	}
-
-	/**
-	 * Tracks whether the matrix data is provided via a storage buffer.
-	 *
-	 * @type {boolean}
-	 */
-	get isStorageMatrix() {
-
-		const { instanceMatrix } = this;
-
-		return instanceMatrix && instanceMatrix.isStorageInstancedBufferAttribute === true;
-
-	}
-
-	/**
-	 * Tracks whether the color data is provided via a storage buffer.
-	 *
-	 * @type {boolean}
-	 */
-	get isStorageColor() {
-
-		const { instanceColor } = this;
-
-		return instanceColor && instanceColor.isStorageInstancedBufferAttribute === true;
-
-	}
-
-	/**
-	 * Setups the internal buffers and nodes and assigns the transformed vertex data
-	 * to predefined node variables for accumulation. That follows the same patterns
-	 * like with morph and skinning nodes.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 */
-	setup( builder ) {
-
-		let { instanceMatrixNode, instanceColorNode } = this;
-
-		// instance matrix
-
-		if ( instanceMatrixNode === null ) {
-
-			instanceMatrixNode = this._createInstanceMatrixNode( true, builder );
-
-			this.instanceMatrixNode = instanceMatrixNode;
-
-		}
-
-		// instance color
-
-		const { instanceColor, isStorageColor } = this;
-
-		if ( instanceColor && instanceColorNode === null ) {
-
-			if ( isStorageColor ) {
-
-				instanceColorNode = storage( instanceColor, 'vec3', Math.max( instanceColor.count, 1 ) ).element( instanceIndex );
-
-			} else {
-
-				const bufferAttribute = new InstancedBufferAttribute( instanceColor.array, 3 );
-
-				const bufferFn = instanceColor.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
-
-				this.bufferColor = bufferAttribute;
-
-				instanceColorNode = vec3( bufferFn( bufferAttribute, 'vec3', 3, 0 ) );
-
-			}
-
-			this.instanceColorNode = instanceColorNode;
-
-		}
-
-		// POSITION
-
-		const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
-		positionLocal.assign( instancePosition );
-
-		if ( builder.needsPreviousData() ) {
-
-			positionPrevious.assign( this.getPreviousInstancedPosition( builder ) );
-
-		}
-
-		// NORMAL
-
-		if ( builder.hasGeometryAttribute( 'normal' ) ) {
-
-			const instanceNormal = transformNormal( normalLocal, instanceMatrixNode );
-
-			// ASSIGNS
-
-			normalLocal.assign( instanceNormal );
-
-		}
-
-		// COLOR
-
-		if ( this.instanceColorNode !== null ) {
-
-			varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode );
-
-		}
-
-	}
-
-	/**
-	 * Checks if the internal buffers require an update.
-	 *
-	 * @param {NodeFrame} frame - The current node frame.
-	 */
-	updateBefore( /*frame*/ ) {
-
-		if ( this.buffer !== null && this.isStorageMatrix !== true ) {
-
-			this.buffer.clearUpdateRanges();
-			this.buffer.updateRanges.push( ... this.instanceMatrix.updateRanges );
-
-			// update version if necessary
-
-			if ( this.instanceMatrix.version !== this.buffer.version ) {
-
-				this.buffer.version = this.instanceMatrix.version;
-
-			}
-
-		}
-
-		if ( this.instanceColor && this.bufferColor !== null && this.isStorageColor !== true ) {
-
-			this.bufferColor.clearUpdateRanges();
-			this.bufferColor.updateRanges.push( ... this.instanceColor.updateRanges );
-
-			if ( this.instanceColor.version !== this.bufferColor.version ) {
-
-				this.bufferColor.version = this.instanceColor.version;
-
-			}
-
-		}
-
-	}
-
-	/**
-	 * Updates velocity-related data if necessary.
-	 *
-	 * @param {NodeFrame} frame - The current node frame.
-	 */
-	update( frame ) {
-
-		if ( this.previousInstanceMatrixNode !== null ) {
-
-			frame.object.previousInstanceMatrix.array.set( this.instanceMatrix.array );
-
-		}
-
-	}
-
-	/**
-	 * Computes the transformed/instanced vertex position of the previous frame.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 * @return {Node<vec3>} The instanced position from the previous frame.
-	 */
-	getPreviousInstancedPosition( builder ) {
-
-		const instancedMesh = builder.object;
-
-		if ( this.previousInstanceMatrixNode === null ) {
-
-			instancedMesh.previousInstanceMatrix = this.instanceMatrix.clone();
-
-			this.previousInstanceMatrixNode = this._createInstanceMatrixNode( false, builder );
-
-		}
-
-		return this.previousInstanceMatrixNode.mul( positionPrevious ).xyz;
-
-	}
-
-	/**
-	 * Creates a node representing the instance matrix data.
-	 *
-	 * @private
-	 * @param {boolean} assignBuffer - Whether the created interleaved buffer should be assigned to the `buffer` member or not.
-	 * @param {NodeBuilder} builder - A reference to the current node builder.
-	 * @return {Node} The instance matrix node.
-	 */
-	_createInstanceMatrixNode( assignBuffer, builder ) {
-
-		let instanceMatrixNode;
-
-		const { instanceMatrix } = this;
-		const { count } = instanceMatrix;
-
-		if ( this.isStorageMatrix ) {
-
-			instanceMatrixNode = storage( instanceMatrix, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
-
-		} else {
-
-			const uniformBufferSize = count * 16 * 4; // count * 16 components * 4 bytes (float)
-
-			if ( uniformBufferSize <= builder.getUniformBufferLimit() ) {
-
-				instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex );
-
-			} else {
-
-				const interleaved = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 );
-
-				if ( assignBuffer === true ) this.buffer = interleaved;
-
-				const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
-
-				const instanceBuffers = [
-					bufferFn( interleaved, 'vec4', 16, 0 ),
-					bufferFn( interleaved, 'vec4', 16, 4 ),
-					bufferFn( interleaved, 'vec4', 16, 8 ),
-					bufferFn( interleaved, 'vec4', 16, 12 )
-				];
-
-				instanceMatrixNode = mat4( ...instanceBuffers );
-
-			}
-
-		}
-
-		return instanceMatrixNode;
-
-	}
-
-}
-
-export default InstanceNode;
-
-/**
- * TSL function for creating an instance node.
- *
- * @tsl
- * @function
- * @param {number} count - The number of instances.
- * @param {InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations.
- * @param {?InstancedBufferAttribute|StorageInstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors.
- * @returns {InstanceNode}
- */
-export const instance = /*@__PURE__*/ nodeProxy( InstanceNode ).setParameterLength( 2, 3 );

+ 0 - 50
src/nodes/accessors/InstancedMeshNode.js

@@ -1,50 +0,0 @@
-import InstanceNode from './InstanceNode.js';
-import { nodeProxy } from '../tsl/TSLBase.js';
-
-/**
- * This is a special version of `InstanceNode` which requires the usage of {@link InstancedMesh}.
- * It allows an easier setup of the instance node.
- *
- * @augments InstanceNode
- */
-class InstancedMeshNode extends InstanceNode {
-
-	static get type() {
-
-		return 'InstancedMeshNode';
-
-	}
-
-	/**
-	 * Constructs a new instanced mesh node.
-	 *
-	 * @param {InstancedMesh} instancedMesh - The instanced mesh.
-	 */
-	constructor( instancedMesh ) {
-
-		const { count, instanceMatrix, instanceColor } = instancedMesh;
-
-		super( count, instanceMatrix, instanceColor );
-
-		/**
-		 * A reference to the instanced mesh.
-		 *
-		 * @type {InstancedMesh}
-		 */
-		this.instancedMesh = instancedMesh;
-
-	}
-
-}
-
-export default InstancedMeshNode;
-
-/**
- * TSL function for creating an instanced mesh node.
- *
- * @tsl
- * @function
- * @param {InstancedMesh} instancedMesh - The instancedMesh.
- * @returns {InstancedMeshNode}
- */
-export const instancedMesh = /*@__PURE__*/ nodeProxy( InstancedMeshNode ).setParameterLength( 1 );

+ 89 - 117
src/nodes/accessors/MorphNode.js → src/nodes/accessors/Morph.js

@@ -1,13 +1,12 @@
-import Node from '../core/Node.js';
-import { NodeUpdateType } from '../core/constants.js';
-import { float, nodeProxy, Fn, ivec2, int, If } from '../tsl/TSLBase.js';
-import { uniform } from '../core/UniformNode.js';
+
+import { float, Fn, ivec2, int, If, uniform } from '../tsl/TSLBase.js';
 import { reference } from './ReferenceNode.js';
+import { Loop } from '../utils/LoopNode.js';
+import { OnObjectUpdate } from '../utils/EventNode.js';
+import { textureLoad } from './TextureNode.js';
 import { positionLocal } from './Position.js';
 import { normalLocal } from './Normal.js';
-import { textureLoad } from './TextureNode.js';
 import { instanceIndex, vertexIndex } from '../core/IndexNode.js';
-import { Loop } from '../utils/LoopNode.js';
 
 import { DataArrayTexture } from '../../textures/DataArrayTexture.js';
 import { Vector2 } from '../../math/Vector2.js';
@@ -16,7 +15,20 @@ import { FloatType } from '../../constants.js';
 
 const _morphTextures = /*@__PURE__*/ new WeakMap();
 const _morphVec4 = /*@__PURE__*/ new Vector4();
+const _morphBaseInfluences = /*@__PURE__*/ new WeakMap();
 
+/**
+ * TSL function that retrieves and scales the morphed attribute (position or normal) texel value.
+ *
+ * @param {Object} params - The parameter object.
+ * @param {Node<texture>} params.bufferMap - The morph target data array texture.
+ * @param {Node<float>} params.influence - The target's animation influence weight.
+ * @param {number} params.stride - The vertex data stride (e.g. 1 or 2).
+ * @param {Node<int>} params.width - The texture width limit.
+ * @param {Node<int>} params.depth - The target layer index (morph target index).
+ * @param {Node<int>} params.offset - The texture offset (e.g. 0 for position, 1 for normal).
+ * @returns {Node<vec3>} The scaled morph target translation value.
+ */
 const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, depth, offset } ) => {
 
 	const texelIndex = int( vertexIndex ).mul( stride ).add( offset );
@@ -30,6 +42,12 @@ const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, dept
 
 } );
 
+/**
+ * Resolves or creates a compiled DataArrayTexture containing encoded vertex morph targets data for WebGL2/WebGPU.
+ *
+ * @param {BufferGeometry} geometry - The geometry to parse.
+ * @returns {Object} The resolved morph targets texture data mapping entry.
+ */
 function getEntry( geometry ) {
 
 	const hasMorphPosition = geometry.morphAttributes.position !== undefined;
@@ -157,154 +175,108 @@ function getEntry( geometry ) {
 }
 
 /**
- * This node implements the vertex transformation shader logic which is required
- * for morph target animation.
+ * TSL object representing a reference to the mesh's morphTargetInfluences array.
  *
- * @augments Node
+ * @type {ReferenceNode<float>}
  */
-class MorphNode extends Node {
-
-	static get type() {
-
-		return 'MorphNode';
+export const morphTargetInfluences = /*@__PURE__*/ reference( 'morphTargetInfluences', 'float' );
 
-	}
-
-	/**
-	 * Constructs a new morph node.
-	 *
-	 * @param {Mesh} mesh - The mesh holding the morph targets.
-	 */
-	constructor( mesh ) {
-
-		super( 'void' );
-
-		/**
-		 * The mesh holding the morph targets.
-		 *
-		 * @type {Mesh}
-		 */
-		this.mesh = mesh;
-
-		/**
-		 * A uniform node which represents the morph base influence value.
-		 *
-		 * @type {UniformNode<float>}
-		 */
-		this.morphBaseInfluence = uniform( 1 );
-
-		/**
-		 * The update type overwritten since morph nodes are updated per object.
-		 *
-		 * @type {string}
-		 */
-		this.updateType = NodeUpdateType.OBJECT;
+/**
+ * TSL function representing the vertex shader morph targets blend setup.
+ * Dynamically computes morph targets weights and updates positionLocal and normalLocal in-place.
+ *
+ * @tsl
+ * @function
+ * @param {Mesh} mesh - The mesh.
+ */
+export const morphReference = /*@__PURE__*/ Fn( ( [ mesh ] ) => {
 
-	}
+	const { geometry } = mesh;
 
-	/**
-	 * Setups the morph node by assigning the transformed vertex data to predefined node variables.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 */
-	setup( builder ) {
+	const hasMorphPosition = geometry.morphAttributes.position !== undefined;
+	const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined;
 
-		const { geometry } = builder;
+	const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
+	const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
 
-		const hasMorphPosition = geometry.morphAttributes.position !== undefined;
-		const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined;
+	if ( morphTargetsCount === 0 ) return;
 
-		const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
-		const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
+	let morphBaseInfluence = _morphBaseInfluences.get( mesh );
 
-		// nodes
+	if ( ! morphBaseInfluence ) {
 
-		const { texture: bufferMap, stride, size } = getEntry( geometry );
+		morphBaseInfluence = uniform( 1 );
+		_morphBaseInfluences.set( mesh, morphBaseInfluence );
 
-		if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence );
-		if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence );
+		OnObjectUpdate( ( { object } ) => {
 
-		const width = int( size.width );
+			if ( object.geometry.morphTargetsRelative ) {
 
-		Loop( morphTargetsCount, ( { i } ) => {
+				morphBaseInfluence.value = 1;
 
-			const influence = float( 0 ).toVar();
+			} else {
 
-			if ( this.mesh.count > 1 && ( this.mesh.morphTexture !== null && this.mesh.morphTexture !== undefined ) ) {
+				morphBaseInfluence.value = 1 - object.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 );
 
-				influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );
+			}
 
-			} else {
+		} );
 
-				influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() );
+	}
 
-			}
+	const { texture: bufferMap, stride, size } = getEntry( geometry );
 
-			If( influence.notEqual( 0 ), () => {
+	if ( hasMorphPosition === true ) positionLocal.mulAssign( morphBaseInfluence );
+	if ( hasMorphNormals === true ) normalLocal.mulAssign( morphBaseInfluence );
 
-				if ( hasMorphPosition === true ) {
+	const width = int( size.width );
 
-					positionLocal.addAssign( getMorph( {
-						bufferMap,
-						influence,
-						stride,
-						width,
-						depth: i,
-						offset: int( 0 )
-					} ) );
+	Loop( morphTargetsCount, ( { i } ) => {
 
-				}
+		const influence = float( 0 ).toVar();
 
-				if ( hasMorphNormals === true ) {
+		if ( mesh.count > 1 && ( mesh.morphTexture !== null && mesh.morphTexture !== undefined ) ) {
 
-					normalLocal.addAssign( getMorph( {
-						bufferMap,
-						influence,
-						stride,
-						width,
-						depth: i,
-						offset: int( 1 )
-					} ) );
+			influence.assign( textureLoad( mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );
 
-				}
+		} else {
 
-			} );
+			influence.assign( morphTargetInfluences.element( i ).toVar() );
 
-		} );
+		}
 
-	}
+		If( influence.notEqual( 0 ), () => {
 
-	/**
-	 * Updates the state of the morphed mesh by updating the base influence.
-	 *
-	 * @param {NodeFrame} frame - The current node frame.
-	 */
-	update( /*frame*/ ) {
+			if ( hasMorphPosition === true ) {
 
-		const morphBaseInfluence = this.morphBaseInfluence;
+				positionLocal.addAssign( getMorph( {
+					bufferMap,
+					influence,
+					stride,
+					width,
+					depth: i,
+					offset: int( 0 )
+				} ) );
 
-		if ( this.mesh.geometry.morphTargetsRelative ) {
+			}
 
-			morphBaseInfluence.value = 1;
+			if ( hasMorphNormals === true ) {
 
-		} else {
+				normalLocal.addAssign( getMorph( {
+					bufferMap,
+					influence,
+					stride,
+					width,
+					depth: i,
+					offset: int( 1 )
+				} ) );
 
-			morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 );
+			}
 
-		}
+		} );
 
-	}
+	} );
 
-}
+}, 'void' );
 
-export default MorphNode;
 
-/**
- * TSL function for creating a morph node.
- *
- * @tsl
- * @function
- * @param {Mesh} mesh - The mesh holding the morph targets.
- * @returns {MorphNode}
- */
-export const morphReference = /*@__PURE__*/ nodeProxy( MorphNode ).setParameterLength( 1 );

+ 263 - 0
src/nodes/accessors/Skinning.js

@@ -0,0 +1,263 @@
+
+import { Fn, add, uniform } from '../tsl/TSLBase.js';
+import { attribute } from '../core/AttributeNode.js';
+import { OnObjectUpdate } from '../utils/EventNode.js';
+import { normalLocal } from './Normal.js';
+import { positionLocal, positionPrevious } from './Position.js';
+import { tangentLocal } from './Tangent.js';
+import { reference, referenceBuffer } from './ReferenceNode.js';
+import { buffer } from './BufferNode.js';
+import { storage } from './StorageBufferNode.js';
+import { instanceIndex } from '../core/IndexNode.js';
+
+import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js';
+
+const _skeletonsUpdated = /*@__PURE__*/ new WeakMap();
+const _previousBoneMatricesData = /*@__PURE__*/ new WeakMap();
+
+/**
+ * Computes the skinned position by applying bone matrices based on weights.
+ *
+ * @param {Node} boneMatrices - The bone matrices buffer or storage node.
+ * @param {Node<vec3>} position - The vertex position to transform.
+ * @param {Node<mat4>} bindMatrix - The bind matrix node.
+ * @param {Node<mat4>} bindMatrixInverse - The inverse bind matrix node.
+ * @param {Node<uvec4>} skinIndex - The skin index attribute.
+ * @param {Node<vec4>} skinWeight - The skin weight attribute.
+ * @returns {Node<vec3>} The skinned position.
+ */
+function getSkinnedPosition( boneMatrices, position, bindMatrix, bindMatrixInverse, skinIndex, skinWeight ) {
+
+	const boneMatX = boneMatrices.element( skinIndex.x );
+	const boneMatY = boneMatrices.element( skinIndex.y );
+	const boneMatZ = boneMatrices.element( skinIndex.z );
+	const boneMatW = boneMatrices.element( skinIndex.w );
+
+	// POSITION
+
+	const skinVertex = bindMatrix.mul( position );
+
+	const skinned = add(
+		boneMatX.mul( skinWeight.x ).mul( skinVertex ),
+		boneMatY.mul( skinWeight.y ).mul( skinVertex ),
+		boneMatZ.mul( skinWeight.z ).mul( skinVertex ),
+		boneMatW.mul( skinWeight.w ).mul( skinVertex )
+	);
+
+	return bindMatrixInverse.mul( skinned ).xyz;
+
+}
+
+/**
+ * Computes the skinned normal and tangent vectors by applying bone matrices based on weights.
+ *
+ * @param {Node} boneMatrices - The bone matrices buffer or storage node.
+ * @param {Node<vec3>} normal - The normal vector in local space.
+ * @param {Node<vec3>} tangent - The tangent vector in local space.
+ * @param {Node<mat4>} bindMatrix - The bind matrix node.
+ * @param {Node<mat4>} bindMatrixInverse - The inverse bind matrix node.
+ * @param {Node<uvec4>} skinIndex - The skin index attribute.
+ * @param {Node<vec4>} skinWeight - The skin weight attribute.
+ * @returns {{skinNormal: Node<vec3>, skinTangent: Node<vec3>}} The skinned normal and tangent.
+ */
+function getSkinnedNormalAndTangent( boneMatrices, normal, tangent, bindMatrix, bindMatrixInverse, skinIndex, skinWeight ) {
+
+	const boneMatX = boneMatrices.element( skinIndex.x );
+	const boneMatY = boneMatrices.element( skinIndex.y );
+	const boneMatZ = boneMatrices.element( skinIndex.z );
+	const boneMatW = boneMatrices.element( skinIndex.w );
+
+	// NORMAL and TANGENT
+
+	let skinMatrix = add(
+		skinWeight.x.mul( boneMatX ),
+		skinWeight.y.mul( boneMatY ),
+		skinWeight.z.mul( boneMatZ ),
+		skinWeight.w.mul( boneMatW )
+	);
+
+	skinMatrix = bindMatrixInverse.mul( skinMatrix ).mul( bindMatrix );
+
+	const skinNormal = skinMatrix.transformDirection( normal ).xyz;
+	const skinTangent = skinMatrix.transformDirection( tangent ).xyz;
+
+	return { skinNormal, skinTangent };
+
+}
+
+/**
+ * Retrieves or initializes the previous frame skinned position node for motion vectors.
+ * Uses a WeakMap to cache previous frame bone matrix arrays and their TSL buffer nodes.
+ *
+ * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
+ * @param {Node<mat4>} bindMatrixNode - The bind matrix node.
+ * @param {Node<mat4>} bindMatrixInverseNode - The inverse bind matrix node.
+ * @param {Node<uvec4>} skinIndexNode - The skin index attribute.
+ * @param {Node<vec4>} skinWeightNode - The skin weight attribute.
+ * @returns {Node<vec3>} The skinned position from the previous frame.
+ */
+function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ) {
+
+	const skeleton = skinnedMesh.skeleton;
+
+	let data = _previousBoneMatricesData.get( skeleton );
+
+	if ( data === undefined ) {
+
+		skeleton.update();
+
+		const previousBoneMatrices = new Float32Array( skeleton.boneMatrices );
+
+		data = {
+			previousBoneMatrices,
+			node: buffer( previousBoneMatrices, 'mat4', skeleton.bones.length )
+		};
+
+		_previousBoneMatricesData.set( skeleton, data );
+
+	}
+
+	return getSkinnedPosition( data.node, positionPrevious, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
+
+}
+
+/**
+ * TSL function representing the standard skeletal animation vertex shader setup.
+ * Transforms positionLocal, normalLocal, and tangentLocal in-place.
+ *
+ * @tsl
+ * @function
+ * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
+ */
+export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => {
+
+	const skinIndexNode = attribute( 'skinIndex', 'uvec4' );
+	const skinWeightNode = attribute( 'skinWeight', 'vec4' );
+	const bindMatrixNode = reference( 'bindMatrix', 'mat4' );
+	const bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' );
+	const boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length );
+
+	OnObjectUpdate( ( { object, frameId } ) => {
+
+		const skeleton = object.skeleton;
+
+		if ( _skeletonsUpdated.get( skeleton ) !== frameId ) {
+
+			_skeletonsUpdated.set( skeleton, frameId );
+
+			const skeletonData = _previousBoneMatricesData.get( skeleton );
+
+			if ( skeletonData !== undefined ) {
+
+				skeletonData.previousBoneMatrices.set( skeleton.boneMatrices );
+
+			}
+
+			skeleton.update();
+
+		}
+
+	} );
+
+	if ( builder.needsPreviousData() ) {
+
+		const previousSkinnedPosition = getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
+
+		positionPrevious.assign( previousSkinnedPosition );
+
+	}
+
+	const skinPosition = getSkinnedPosition( boneMatricesNode, positionLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
+	positionLocal.assign( skinPosition );
+
+	if ( builder.hasGeometryAttribute( 'normal' ) ) {
+
+		const { skinNormal, skinTangent } = getSkinnedNormalAndTangent( boneMatricesNode, normalLocal, tangentLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
+
+		normalLocal.assign( skinNormal );
+
+		if ( builder.hasGeometryAttribute( 'tangent' ) ) {
+
+			tangentLocal.assign( skinTangent );
+
+		}
+
+	}
+
+}, 'void' );
+
+/**
+ * TSL function that computes skeletal animation for custom compute passes.
+ *
+ * @tsl
+ * @function
+ * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
+ * @param {Node<vec3>} [toPosition=null] - The target position node to assign.
+ * @returns {Node<vec3>} The computed skinned position node.
+ */
+export const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = null ], builder ) => {
+
+	const positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
+	const skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
+	const skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
+	const bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' );
+	const bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' );
+	const boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length );
+
+	const skeleton = skinnedMesh.skeleton;
+
+	OnObjectUpdate( ( { frameId } ) => {
+
+		if ( _skeletonsUpdated.get( skeleton ) !== frameId ) {
+
+			_skeletonsUpdated.set( skeleton, frameId );
+
+			const state = _previousBoneMatricesData.get( skeleton );
+
+			if ( state !== undefined ) {
+
+				state.previousBoneMatrices.set( skeleton.boneMatrices );
+
+			}
+
+			skeleton.update();
+
+		}
+
+	} );
+
+	if ( builder.needsPreviousData() ) {
+
+		const previousSkinnedPosition = getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
+
+		positionPrevious.assign( previousSkinnedPosition );
+
+	}
+
+	const skinPosition = getSkinnedPosition( boneMatricesNode, positionNode, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
+
+	if ( toPosition !== null ) {
+
+		toPosition.assign( skinPosition );
+
+	}
+
+	if ( builder.hasGeometryAttribute( 'normal' ) ) {
+
+		const { skinNormal, skinTangent } = getSkinnedNormalAndTangent( boneMatricesNode, normalLocal, tangentLocal, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode );
+
+		normalLocal.assign( skinNormal );
+
+		if ( builder.hasGeometryAttribute( 'tangent' ) ) {
+
+			tangentLocal.assign( skinTangent );
+
+		}
+
+	}
+
+	return skinPosition;
+
+} );
+
+

+ 0 - 328
src/nodes/accessors/SkinningNode.js

@@ -1,328 +0,0 @@
-import Node from '../core/Node.js';
-import { NodeUpdateType } from '../core/constants.js';
-import { nodeObject } from '../tsl/TSLBase.js';
-import { attribute } from '../core/AttributeNode.js';
-import { reference, referenceBuffer } from './ReferenceNode.js';
-import { add } from '../math/OperatorNode.js';
-import { normalLocal } from './Normal.js';
-import { positionLocal, positionPrevious } from './Position.js';
-import { tangentLocal } from './Tangent.js';
-import { uniform } from '../core/UniformNode.js';
-import { buffer } from './BufferNode.js';
-import { storage } from './StorageBufferNode.js';
-import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js';
-import { instanceIndex } from '../core/IndexNode.js';
-
-const _frameId = new WeakMap();
-
-/**
- * This node implements the vertex transformation shader logic which is required
- * for skinning/skeletal animation.
- *
- * @augments Node
- */
-class SkinningNode extends Node {
-
-	static get type() {
-
-		return 'SkinningNode';
-
-	}
-
-	/**
-	 * Constructs a new skinning node.
-	 *
-	 * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
-	 */
-	constructor( skinnedMesh ) {
-
-		super( 'void' );
-
-		/**
-		 * The skinned mesh.
-		 *
-		 * @type {SkinnedMesh}
-		 */
-		this.skinnedMesh = skinnedMesh;
-
-		/**
-		 * The update type overwritten since skinning nodes are updated per object.
-		 *
-		 * @type {string}
-		 */
-		this.updateType = NodeUpdateType.OBJECT;
-
-		//
-
-		/**
-		 * The skin index attribute.
-		 *
-		 * @type {AttributeNode}
-		 */
-		this.skinIndexNode = attribute( 'skinIndex', 'uvec4' );
-
-		/**
-		 * The skin weight attribute.
-		 *
-		 * @type {AttributeNode}
-		 */
-		this.skinWeightNode = attribute( 'skinWeight', 'vec4' );
-
-		/**
-		 * The bind matrix node.
-		 *
-		 * @type {Node<mat4>}
-		 */
-		this.bindMatrixNode = reference( 'bindMatrix', 'mat4' );
-
-		/**
-		 * The bind matrix inverse node.
-		 *
-		 * @type {Node<mat4>}
-		 */
-		this.bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' );
-
-		/**
-		 * The bind matrices as a uniform buffer node.
-		 *
-		 * @type {Node}
-		 */
-		this.boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length );
-
-		/**
-		 * The current vertex position in local space.
-		 *
-		 * @type {Node<vec3>}
-		 */
-		this.positionNode = positionLocal;
-
-		/**
-		 * The result of vertex position in local space.
-		 *
-		 * @type {Node<vec3>}
-		 */
-		this.toPositionNode = positionLocal;
-
-		/**
-		 * The previous bind matrices as a uniform buffer node.
-		 * Required for computing motion vectors.
-		 *
-		 * @type {?Node}
-		 * @default null
-		 */
-		this.previousBoneMatricesNode = null;
-
-	}
-
-	/**
-	 * Transforms the given vertex position via skinning.
-	 *
-	 * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices
-	 * @param {Node<vec3>} [position=this.positionNode] - The vertex position in local space.
-	 * @return {Node<vec3>} The transformed vertex position.
-	 */
-	getSkinnedPosition( boneMatrices = this.boneMatricesNode, position = this.positionNode ) {
-
-		const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this;
-
-		const boneMatX = boneMatrices.element( skinIndexNode.x );
-		const boneMatY = boneMatrices.element( skinIndexNode.y );
-		const boneMatZ = boneMatrices.element( skinIndexNode.z );
-		const boneMatW = boneMatrices.element( skinIndexNode.w );
-
-		// POSITION
-
-		const skinVertex = bindMatrixNode.mul( position );
-
-		const skinned = add(
-			boneMatX.mul( skinWeightNode.x ).mul( skinVertex ),
-			boneMatY.mul( skinWeightNode.y ).mul( skinVertex ),
-			boneMatZ.mul( skinWeightNode.z ).mul( skinVertex ),
-			boneMatW.mul( skinWeightNode.w ).mul( skinVertex )
-		);
-
-		return bindMatrixInverseNode.mul( skinned ).xyz;
-
-	}
-
-	/**
-	 * Transforms the given vertex normal and tangent via skinning.
-	 *
-	 * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices
-	 * @param {Node<vec3>} [normal=normalLocal] - The vertex normal in local space.
-	 * @param {Node<vec3>} [tangent=tangentLocal] - The vertex tangent in local space.
-	 * @return {{skinNormal: Node<vec3>, skinTangent:Node<vec3>}} The transformed vertex normal and tangent.
-	 */
-	getSkinnedNormalAndTangent( boneMatrices = this.boneMatricesNode, normal = normalLocal, tangent = tangentLocal ) {
-
-		const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this;
-
-		const boneMatX = boneMatrices.element( skinIndexNode.x );
-		const boneMatY = boneMatrices.element( skinIndexNode.y );
-		const boneMatZ = boneMatrices.element( skinIndexNode.z );
-		const boneMatW = boneMatrices.element( skinIndexNode.w );
-
-		// NORMAL and TANGENT
-
-		let skinMatrix = add(
-			skinWeightNode.x.mul( boneMatX ),
-			skinWeightNode.y.mul( boneMatY ),
-			skinWeightNode.z.mul( boneMatZ ),
-			skinWeightNode.w.mul( boneMatW )
-		);
-
-		skinMatrix = bindMatrixInverseNode.mul( skinMatrix ).mul( bindMatrixNode );
-
-		const skinNormal = skinMatrix.transformDirection( normal ).xyz;
-		const skinTangent = skinMatrix.transformDirection( tangent ).xyz;
-
-		return { skinNormal, skinTangent };
-
-	}
-
-	/**
-	 * Computes the transformed/skinned vertex position of the previous frame.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 * @return {Node<vec3>} The skinned position from the previous frame.
-	 */
-	getPreviousSkinnedPosition( builder ) {
-
-		const skinnedMesh = builder.object;
-
-		if ( this.previousBoneMatricesNode === null ) {
-
-			skinnedMesh.skeleton.previousBoneMatrices = new Float32Array( skinnedMesh.skeleton.boneMatrices );
-
-			this.previousBoneMatricesNode = referenceBuffer( 'skeleton.previousBoneMatrices', 'mat4', skinnedMesh.skeleton.bones.length );
-
-		}
-
-		return this.getSkinnedPosition( this.previousBoneMatricesNode, positionPrevious );
-
-	}
-
-	/**
-	 * Setups the skinning node by assigning the transformed vertex data to predefined node variables.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 * @return {Node<vec3>} The transformed vertex position.
-	 */
-	setup( builder ) {
-
-		if ( builder.needsPreviousData() ) {
-
-			positionPrevious.assign( this.getPreviousSkinnedPosition( builder ) );
-
-		}
-
-		const skinPosition = this.getSkinnedPosition();
-
-		if ( this.toPositionNode ) this.toPositionNode.assign( skinPosition );
-
-		//
-
-		if ( builder.hasGeometryAttribute( 'normal' ) ) {
-
-			const { skinNormal, skinTangent } = this.getSkinnedNormalAndTangent();
-
-			normalLocal.assign( skinNormal );
-
-			if ( builder.hasGeometryAttribute( 'tangent' ) ) {
-
-				tangentLocal.assign( skinTangent );
-
-			}
-
-		}
-
-		return skinPosition;
-
-	}
-
-	/**
-	 * Generates the code snippet of the skinning node.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 * @param {string} output - The current output.
-	 * @return {string} The generated code snippet.
-	 */
-	generate( builder, output ) {
-
-		if ( output !== 'void' ) {
-
-			return super.generate( builder, output );
-
-		}
-
-	}
-
-	/**
-	 * Updates the state of the skinned mesh by updating the skeleton once per frame.
-	 *
-	 * @param {NodeFrame} frame - The current node frame.
-	 */
-	update( frame ) {
-
-		const skeleton = frame.object && frame.object.skeleton ? frame.object.skeleton : this.skinnedMesh.skeleton;
-
-		if ( _frameId.get( skeleton ) === frame.frameId ) return;
-
-		_frameId.set( skeleton, frame.frameId );
-
-		if ( this.previousBoneMatricesNode !== null ) {
-
-			if ( skeleton.previousBoneMatrices === null ) {
-
-				// cloned skeletons miss "previousBoneMatrices" in their first updated
-
-				skeleton.previousBoneMatrices = new Float32Array( skeleton.boneMatrices );
-
-			}
-
-			skeleton.previousBoneMatrices.set( skeleton.boneMatrices );
-
-
-		}
-
-		skeleton.update();
-
-	}
-
-}
-
-export default SkinningNode;
-
-/**
- * TSL function for creating a skinning node.
- *
- * @tsl
- * @function
- * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
- * @returns {SkinningNode}
- */
-export const skinning = ( skinnedMesh ) => new SkinningNode( skinnedMesh );
-
-/**
- * TSL function for computing skinning.
- *
- * @tsl
- * @function
- * @param {SkinnedMesh} skinnedMesh - The skinned mesh.
- * @param {Node<vec3>} [toPosition=null] - The target position.
- * @returns {SkinningNode}
- */
-export const computeSkinning = ( skinnedMesh, toPosition = null ) => {
-
-	const node = new SkinningNode( skinnedMesh );
-	node.positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
-	node.skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
-	node.skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar();
-	node.bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' );
-	node.bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' );
-	node.boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length );
-	node.toPositionNode = toPosition;
-
-	return nodeObject( node );
-
-};

+ 0 - 11
src/objects/InstancedMesh.js

@@ -56,15 +56,6 @@ class InstancedMesh extends Mesh {
 		 */
 		this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );
 
-		/**
-		 * Represents the local transformation of all instances of the previous frame.
-		 * Required for computing velocity. Maintained in {@link InstanceNode}.
-		 *
-		 * @type {?InstancedBufferAttribute}
-		 * @default null
-		 */
-		this.previousInstanceMatrix = null;
-
 		/**
 		 * Represents the color of all instances. You have to set its
 		 * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data
@@ -194,8 +185,6 @@ class InstancedMesh extends Mesh {
 
 		this.instanceMatrix.copy( source.instanceMatrix );
 
-		if ( source.previousInstanceMatrix !== null ) this.previousInstanceMatrix = source.previousInstanceMatrix.clone();
-
 		if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone();
 		if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
 

+ 0 - 9
src/objects/Skeleton.js

@@ -70,15 +70,6 @@ class Skeleton {
 		 */
 		this.boneMatrices = null;
 
-		/**
-		 * An array buffer holding the bone data of the previous frame.
-		 * Required for computing velocity. Maintained in {@link SkinningNode}.
-		 *
-		 * @type {?Float32Array}
-		 * @default null
-		 */
-		this.previousBoneMatrices = null;
-
 		/**
 		 * A texture holding the bone data for use
 		 * in the vertex shader.

+ 1 - 1
src/renderers/common/RenderObject.js

@@ -684,7 +684,7 @@ class RenderObject {
 
 		// structural equality isn't sufficient for morph targets since the
 		// data are maintained in textures. only if the targets are all equal
-		// the texture and thus the instance of `MorphNode` can be shared.
+		// the texture and thus the `morphReference` can be shared.
 
 		for ( const name of Object.keys( geometry.morphAttributes ).sort() ) {
 

粤ICP备19079148号