diff --git a/examples/jsm/loaders/PCDLoader.js b/examples/jsm/loaders/PCDLoader.js index cd8a7a93344cac..ef7ca1cc7cfdaf 100644 --- a/examples/jsm/loaders/PCDLoader.js +++ b/examples/jsm/loaders/PCDLoader.js @@ -269,7 +269,7 @@ class PCDLoader extends Loader { // remove comments - PCDheader.str = PCDheader.str.replace( /#.*/gi, '' ); + PCDheader.str = PCDheader.str.replace( /^#.*/gm, '' ); // parse diff --git a/examples/screenshots/webgpu_backdrop_area.jpg b/examples/screenshots/webgpu_backdrop_area.jpg index b02962b19a48d1..402a809ebce565 100644 Binary files a/examples/screenshots/webgpu_backdrop_area.jpg and b/examples/screenshots/webgpu_backdrop_area.jpg differ diff --git a/examples/webgpu_backdrop_area.html b/examples/webgpu_backdrop_area.html index 0a1e69326ad162..2e052b39283a68 100644 --- a/examples/webgpu_backdrop_area.html +++ b/examples/webgpu_backdrop_area.html @@ -150,12 +150,12 @@ const materials = { 'blurred': blurredBlur, - 'depth': depthMaterial, 'checker': checkerMaterial, + 'depth': depthMaterial, 'pixel': pixelMaterial }; - const options = { material: 'blurred' }; + const options = { material: 'checker' }; box.material = materials[ options.material ]; const gui = renderer.inspector.createParameters( 'Scene settings' ); diff --git a/src/nodes/accessors/Skinning.js b/src/nodes/accessors/Skinning.js index 5b349a954d8e34..437e0c02c98c38 100644 --- a/src/nodes/accessors/Skinning.js +++ b/src/nodes/accessors/Skinning.js @@ -1,5 +1,5 @@ -import { Fn, add, uniform, mat3 } from '../tsl/TSLBase.js'; +import { Fn, add, uniform, int, ivec2, mat3, mat4 } from '../tsl/TSLBase.js'; import { attribute } from '../core/AttributeNode.js'; import { OnObjectUpdate } from '../utils/EventNode.js'; import { normalLocal } from './Normal.js'; @@ -8,13 +8,87 @@ import { tangentLocal } from './Tangent.js'; import { reference, referenceBuffer } from './ReferenceNode.js'; import { buffer } from './BufferNode.js'; import { storage } from './StorageBufferNode.js'; +import { texture } from './TextureNode.js'; +import { textureSize } from './TextureSizeNode.js'; import { instanceIndex } from '../core/IndexNode.js'; import { InstancedBufferAttribute } from '../../core/InstancedBufferAttribute.js'; +import { DataTexture } from '../../textures/DataTexture.js'; +import { RGBAFormat, FloatType } from '../../constants.js'; const _skeletonsUpdated = /*@__PURE__*/ new WeakMap(); const _previousBoneMatricesData = /*@__PURE__*/ new WeakMap(); +/** + * Creates an accessor for bone matrices stored in a bone texture. + * + * @param {TextureNode} boneTexture - The bone texture node. + * @returns {Object} An accessor with the same `element()` interface as a buffer node. + */ +function getBoneTextureMatrices( boneTexture ) { + + return { + element: ( i ) => { + + const size = int( textureSize( boneTexture ).x ).toConst(); + const j = int( i ).mul( 4 ).toConst(); + const y = j.div( size ).toConst(); + const x = j.sub( y.mul( size ) ).toConst(); + + return mat4( + boneTexture.load( ivec2( x, y ) ), + boneTexture.load( ivec2( x.add( 1 ), y ) ), + boneTexture.load( ivec2( x.add( 2 ), y ) ), + boneTexture.load( ivec2( x.add( 3 ), y ) ) + ); + + } + }; + +} + +/** + * Creates the bone matrices node. Skeletons that fit within the uniform buffer limit + * use a uniform buffer, larger skeletons fall back to a bone texture. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Skeleton} skeleton - The skeleton. + * @returns {Object} The bone matrices node. + */ +function getBoneMatricesNode( builder, skeleton ) { + + let node; + + const uniformBufferSize = skeleton.bones.length * 16 * 4; + + if ( uniformBufferSize <= builder.getUniformBufferLimit() ) { + + node = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skeleton.bones.length ); + + } else { + + if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); + + const boneTexture = texture( skeleton.boneTexture ); + + OnObjectUpdate( ( { object } ) => { + + const skeleton = object.skeleton; + + if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); + + boneTexture.value = skeleton.boneTexture; + + } ); + + node = getBoneTextureMatrices( boneTexture ); + + } + + return node; + +} + /** * Computes the skinned position by applying bone matrices based on weights. * @@ -91,6 +165,7 @@ function getSkinnedNormalAndTangent( boneMatrices, normal, tangent, bindMatrix, * 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 {NodeBuilder} builder - The current node builder. * @param {SkinnedMesh} skinnedMesh - The skinned mesh. * @param {Node} bindMatrixNode - The bind matrix node. * @param {Node} bindMatrixInverseNode - The inverse bind matrix node. @@ -98,7 +173,7 @@ function getSkinnedNormalAndTangent( boneMatrices, normal, tangent, bindMatrix, * @param {Node} skinWeightNode - The skin weight attribute. * @returns {Node} The skinned position from the previous frame. */ -function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ) { +function getPreviousSkinnedPosition( builder, skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ) { const skeleton = skinnedMesh.skeleton; @@ -108,12 +183,35 @@ function getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInve skeleton.update(); - const previousBoneMatrices = new Float32Array( skeleton.boneMatrices ); + const uniformBufferSize = skeleton.bones.length * 16 * 4; + + if ( uniformBufferSize <= builder.getUniformBufferLimit() ) { + + const previousBoneMatrices = new Float32Array( skeleton.boneMatrices ); + + data = { + previousBoneMatrices, + previousBoneTexture: null, + node: buffer( previousBoneMatrices, 'mat4', skeleton.bones.length ) + }; - data = { - previousBoneMatrices, - node: buffer( previousBoneMatrices, 'mat4', skeleton.bones.length ) - }; + } else { + + if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); + + const { width, height } = skeleton.boneTexture.image; + + const previousBoneMatrices = new Float32Array( skeleton.boneMatrices ); + const previousBoneTexture = new DataTexture( previousBoneMatrices, width, height, RGBAFormat, FloatType ); + previousBoneTexture.needsUpdate = true; + + data = { + previousBoneMatrices, + previousBoneTexture, + node: getBoneTextureMatrices( texture( previousBoneTexture ) ) + }; + + } _previousBoneMatricesData.set( skeleton, data ); @@ -137,7 +235,7 @@ export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { 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 ); + const boneMatricesNode = getBoneMatricesNode( builder, skinnedMesh.skeleton ); OnObjectUpdate( ( { object, frameId } ) => { @@ -153,6 +251,12 @@ export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { skeletonData.previousBoneMatrices.set( skeleton.boneMatrices ); + if ( skeletonData.previousBoneTexture !== null ) { + + skeletonData.previousBoneTexture.needsUpdate = true; + + } + } skeleton.update(); @@ -163,7 +267,7 @@ export const skinning = /*@__PURE__*/ Fn( ( [ skinnedMesh ], builder ) => { if ( builder.needsPreviousData() ) { - const previousSkinnedPosition = getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + const previousSkinnedPosition = getPreviousSkinnedPosition( builder, skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); positionPrevious.assign( previousSkinnedPosition ); @@ -220,6 +324,12 @@ export const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = n state.previousBoneMatrices.set( skeleton.boneMatrices ); + if ( state.previousBoneTexture !== null ) { + + state.previousBoneTexture.needsUpdate = true; + + } + } skeleton.update(); @@ -230,7 +340,7 @@ export const computeSkinning = /*@__PURE__*/ Fn( ( [ skinnedMesh, toPosition = n if ( builder.needsPreviousData() ) { - const previousSkinnedPosition = getPreviousSkinnedPosition( skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); + const previousSkinnedPosition = getPreviousSkinnedPosition( builder, skinnedMesh, bindMatrixNode, bindMatrixInverseNode, skinIndexNode, skinWeightNode ); positionPrevious.assign( previousSkinnedPosition );