From c944e5acd2e80bffcbe56473e9151cdf83369721 Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 25 Jun 2023 01:20:56 -0300 Subject: [PATCH] WebGPURenderer: MorphNode 1/2 and updates (#26325) * Added: instancedBufferAttribute() and instancedDynamicBufferAttribute() * TSL: vertexIndex * Background: Fix color conversion * NodeMaterial: Added flatShading * Added MorphNode * Added `webgpu_morphtargets` example * Update examples/jsm/nodes/accessors/BufferAttributeNode.js Co-authored-by: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> --------- Co-authored-by: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> --- examples/files.json | 1 + examples/jsm/nodes/Nodes.js | 5 +- .../nodes/accessors/BufferAttributeNode.js | 29 ++- examples/jsm/nodes/accessors/InstanceNode.js | 4 +- examples/jsm/nodes/accessors/MorphNode.js | 70 +++++++ examples/jsm/nodes/core/IndexNode.js | 66 +++++++ examples/jsm/nodes/core/InstanceIndexNode.js | 45 ----- examples/jsm/nodes/core/NodeBuilder.js | 6 + examples/jsm/nodes/geometry/RangeNode.js | 2 +- examples/jsm/nodes/materials/NodeMaterial.js | 28 ++- examples/jsm/renderers/common/Background.js | 4 +- .../renderers/webgl/nodes/GLSLNodeBuilder.js | 6 + .../renderers/webgl/nodes/WebGLNodeBuilder.js | 6 + .../renderers/webgpu/nodes/WGSLNodeBuilder.js | 12 ++ examples/screenshots/webgpu_morphtargets.jpg | Bin 0 -> 3413 bytes examples/webgpu_morphtargets.html | 182 ++++++++++++++++++ test/e2e/puppeteer.js | 1 + 17 files changed, 402 insertions(+), 65 deletions(-) create mode 100644 examples/jsm/nodes/accessors/MorphNode.js create mode 100644 examples/jsm/nodes/core/IndexNode.js delete mode 100644 examples/jsm/nodes/core/InstanceIndexNode.js create mode 100644 examples/screenshots/webgpu_morphtargets.jpg create mode 100644 examples/webgpu_morphtargets.html diff --git a/examples/files.json b/examples/files.json index 1367957c6db5ab..6d6d784a765f07 100644 --- a/examples/files.json +++ b/examples/files.json @@ -325,6 +325,7 @@ "webgpu_loader_gltf_compressed", "webgpu_materials", "webgpu_materials_video", + "webgpu_morphtargets", "webgpu_particles", "webgpu_rtt", "webgpu_sandbox", diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 25af78aedb503b..caee1e415d3316 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -11,7 +11,7 @@ export { default as BypassNode, bypass } from './core/BypassNode.js'; export { default as CacheNode, cache } from './core/CacheNode.js'; export { default as ConstNode } from './core/ConstNode.js'; export { default as ContextNode, context, label } from './core/ContextNode.js'; -export { default as InstanceIndexNode, instanceIndex } from './core/InstanceIndexNode.js'; +export { default as IndexNode, vertexIndex, instanceIndex } from './core/IndexNode.js'; export { default as LightingModel, lightingModel } from './core/LightingModel.js'; export { default as Node, addNodeClass, createNodeFromType } from './core/Node.js'; export { default as NodeAttribute } from './core/NodeAttribute.js'; @@ -63,7 +63,7 @@ export * from './shadernode/ShaderNode.js'; // accessors export { default as BitangentNode, bitangentGeometry, bitangentLocal, bitangentView, bitangentWorld, transformedBitangentView, transformedBitangentWorld } from './accessors/BitangentNode.js'; -export { default as BufferAttributeNode, bufferAttribute, dynamicBufferAttribute } from './accessors/BufferAttributeNode.js'; +export { default as BufferAttributeNode, bufferAttribute, dynamicBufferAttribute, instancedBufferAttribute, instancedDynamicBufferAttribute } from './accessors/BufferAttributeNode.js'; export { default as BufferNode, buffer } from './accessors/BufferNode.js'; export { default as CameraNode, cameraProjectionMatrix, cameraViewMatrix, cameraNormalMatrix, cameraWorldMatrix, cameraPosition, cameraNear, cameraFar } from './accessors/CameraNode.js'; export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js'; @@ -71,6 +71,7 @@ export { default as ExtendedMaterialNode, materialNormal } from './accessors/Ext export { default as InstanceNode, instance } from './accessors/InstanceNode.js'; export { default as MaterialNode, materialUV, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialReflectivity, materialRoughness, materialMetalness, materialRotation } from './accessors/MaterialNode.js'; export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js'; +export { default as MorphNode, morph } from './accessors/MorphNode.js'; export { default as TextureBicubicNode, textureBicubic } from './accessors/TextureBicubicNode.js'; export { default as ModelNode, modelDirection, modelViewMatrix, modelNormalMatrix, modelWorldMatrix, modelPosition, modelViewPosition, modelScale } from './accessors/ModelNode.js'; export { default as ModelViewProjectionNode, modelViewProjection } from './accessors/ModelViewProjectionNode.js'; diff --git a/examples/jsm/nodes/accessors/BufferAttributeNode.js b/examples/jsm/nodes/accessors/BufferAttributeNode.js index c46f40d38ef0d0..8587a29df2c264 100644 --- a/examples/jsm/nodes/accessors/BufferAttributeNode.js +++ b/examples/jsm/nodes/accessors/BufferAttributeNode.js @@ -17,6 +17,7 @@ class BufferAttributeNode extends InputNode { this.bufferOffset = bufferOffset; this.usage = StaticDrawUsage; + this.instanced = false; } @@ -34,7 +35,7 @@ class BufferAttributeNode extends InputNode { buffer.setUsage( this.usage ); this.attribute = bufferAttribute; - this.attribute.isInstancedBufferAttribute = true; // @TODO: Add a possible: InstancedInterleavedBufferAttribute + this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute } @@ -69,18 +70,30 @@ class BufferAttributeNode extends InputNode { } + setUsage( value ) { + + this.usage = value; + + return this; + + } + + setInstanced( value ) { + + this.instanced = value; + + return this; + + } + } export default BufferAttributeNode; export const bufferAttribute = ( array, type, stride, offset ) => nodeObject( new BufferAttributeNode( array, type, stride, offset ) ); -export const dynamicBufferAttribute = ( array, type, stride, offset ) => { - - const node = bufferAttribute( array, type, stride, offset ); - node.usage = DynamicDrawUsage; - - return node; +export const dynamicBufferAttribute = ( array, type, stride, offset ) => bufferAttribute( array, type, stride, offset ).setUsage( DynamicDrawUsage ); -}; +export const instancedBufferAttribute = ( array, type, stride, offset ) => bufferAttribute( array, type, stride, offset ).setInstanced( true ); +export const instancedDynamicBufferAttribute = ( array, type, stride, offset ) => dynamicBufferAttribute( array, type, stride, offset ).setInstanced( true ); addNodeClass( BufferAttributeNode ); diff --git a/examples/jsm/nodes/accessors/InstanceNode.js b/examples/jsm/nodes/accessors/InstanceNode.js index 4c80cd6f818157..fbef2fe47abf25 100644 --- a/examples/jsm/nodes/accessors/InstanceNode.js +++ b/examples/jsm/nodes/accessors/InstanceNode.js @@ -1,5 +1,5 @@ import Node, { addNodeClass } from '../core/Node.js'; -import { bufferAttribute, dynamicBufferAttribute } from './BufferAttributeNode.js'; +import { instancedBufferAttribute, instancedDynamicBufferAttribute } from './BufferAttributeNode.js'; import { normalLocal } from './NormalNode.js'; import { positionLocal } from './PositionNode.js'; import { nodeProxy, vec3, mat3, mat4 } from '../shadernode/ShaderNode.js'; @@ -27,7 +27,7 @@ class InstanceNode extends Node { const instaceAttribute = instanceMesh.instanceMatrix; const array = instaceAttribute.array; - const bufferFn = instaceAttribute.usage === DynamicDrawUsage ? dynamicBufferAttribute : bufferAttribute; + const bufferFn = instaceAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; const instanceBuffers = [ // F.Signature -> bufferAttribute( array, type, stride, offset ) diff --git a/examples/jsm/nodes/accessors/MorphNode.js b/examples/jsm/nodes/accessors/MorphNode.js new file mode 100644 index 00000000000000..016829ff4a6eb5 --- /dev/null +++ b/examples/jsm/nodes/accessors/MorphNode.js @@ -0,0 +1,70 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { NodeUpdateType } from '../core/constants.js'; +import { nodeProxy } from '../shadernode/ShaderNode.js'; +import { uniform } from '../core/UniformNode.js'; +import { reference } from './ReferenceNode.js'; +import { bufferAttribute } from './BufferAttributeNode.js'; +import { positionLocal } from './PositionNode.js'; + +class MorphNode extends Node { + + constructor( mesh ) { + + super( 'void' ); + + this.mesh = mesh; + this.morphBaseInfluence = uniform( 1 ); + + this.updateType = NodeUpdateType.OBJECT; + + } + + constructAttribute( builder, name, assignNode = positionLocal ) { + + const mesh = this.mesh; + const attributes = mesh.geometry.morphAttributes[ name ]; + + builder.stack.assign( assignNode, assignNode.mul( this.morphBaseInfluence ) ); + + for ( let i = 0; i < attributes.length; i ++ ) { + + const attribute = attributes[ i ]; + + const bufferAttrib = bufferAttribute( attribute.array, 'vec3' ); + const influence = reference( i, 'float', mesh.morphTargetInfluences ); + + builder.stack.assign( assignNode, assignNode.add( bufferAttrib.mul( influence ) ) ); + + } + + } + + construct( builder ) { + + this.constructAttribute( builder, 'position' ); + + } + + update() { + + const morphBaseInfluence = this.morphBaseInfluence; + + if ( this.mesh.geometry.morphTargetsRelative ) { + + morphBaseInfluence.value = 1; + + } else { + + morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 ); + + } + + } + +} + +export default MorphNode; + +export const morph = nodeProxy( MorphNode ); + +addNodeClass( MorphNode ); diff --git a/examples/jsm/nodes/core/IndexNode.js b/examples/jsm/nodes/core/IndexNode.js new file mode 100644 index 00000000000000..f88233dc7e0397 --- /dev/null +++ b/examples/jsm/nodes/core/IndexNode.js @@ -0,0 +1,66 @@ +import Node, { addNodeClass } from './Node.js'; +import { varying } from './VaryingNode.js'; +import { nodeImmutable } from '../shadernode/ShaderNode.js'; + +class IndexNode extends Node { + + constructor( scope ) { + + super( 'uint' ); + + this.scope = scope; + + this.isInstanceIndexNode = true; + + } + + generate( builder ) { + + const nodeType = this.getNodeType( builder ); + const scope = this.scope; + + let propertyName; + + if ( scope === IndexNode.VERTEX ) { + + propertyName = builder.getVertexIndex(); + + } else if ( scope === IndexNode.INSTANCE ) { + + propertyName = builder.getInstanceIndex(); + + } else { + + throw new Error( 'THREE.IndexNode: Unknown scope: ' + scope ); + + } + + let output; + + if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) { + + output = propertyName; + + } else { + + const nodeVarying = varying( this ); + + output = nodeVarying.build( builder, nodeType ); + + } + + return output; + + } + +} + +IndexNode.VERTEX = 'vertex'; +IndexNode.INSTANCE = 'instance'; + +export default IndexNode; + +export const vertexIndex = nodeImmutable( IndexNode, IndexNode.VERTEX ); +export const instanceIndex = nodeImmutable( IndexNode, IndexNode.INSTANCE ); + +addNodeClass( IndexNode ); diff --git a/examples/jsm/nodes/core/InstanceIndexNode.js b/examples/jsm/nodes/core/InstanceIndexNode.js deleted file mode 100644 index a9ea6460393fdd..00000000000000 --- a/examples/jsm/nodes/core/InstanceIndexNode.js +++ /dev/null @@ -1,45 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { varying } from '../core/VaryingNode.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class InstanceIndexNode extends Node { - - constructor() { - - super( 'uint' ); - - this.isInstanceIndexNode = true; - - } - - generate( builder ) { - - const nodeType = this.getNodeType( builder ); - - const propertyName = builder.getInstanceIndex(); - - let output = null; - - if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) { - - output = propertyName; - - } else { - - const nodeVarying = varying( this ); - - output = nodeVarying.build( builder, nodeType ); - - } - - return output; - - } - -} - -export default InstanceIndexNode; - -export const instanceIndex = nodeImmutable( InstanceIndexNode ); - -addNodeClass( InstanceIndexNode ); diff --git a/examples/jsm/nodes/core/NodeBuilder.js b/examples/jsm/nodes/core/NodeBuilder.js index ccd2714582510b..eabec7627764e7 100644 --- a/examples/jsm/nodes/core/NodeBuilder.js +++ b/examples/jsm/nodes/core/NodeBuilder.js @@ -234,6 +234,12 @@ class NodeBuilder { } + getVertexIndex() { + + console.warn( 'Abstract function.' ); + + } + getInstanceIndex() { console.warn( 'Abstract function.' ); diff --git a/examples/jsm/nodes/geometry/RangeNode.js b/examples/jsm/nodes/geometry/RangeNode.js index b875fb44482a79..a7de4fd2e0863a 100644 --- a/examples/jsm/nodes/geometry/RangeNode.js +++ b/examples/jsm/nodes/geometry/RangeNode.js @@ -2,7 +2,7 @@ import Node, { addNodeClass } from '../core/Node.js'; import { getValueType } from '../core/NodeUtils.js'; import { buffer } from '../accessors/BufferNode.js'; //import { bufferAttribute } from '../accessors/BufferAttributeNode.js'; -import { instanceIndex } from '../core/InstanceIndexNode.js'; +import { instanceIndex } from '../core/IndexNode.js'; import { nodeProxy, float } from '../shadernode/ShaderNode.js'; import { Vector4, MathUtils } from 'three'; diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index b07e529972b985..016754fa2ef107 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -7,12 +7,13 @@ import { materialAlphaTest, materialColor, materialOpacity, materialEmissive } f import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js'; import { transformedNormalView } from '../accessors/NormalNode.js'; import { instance } from '../accessors/InstanceNode.js'; -import { positionLocal } from '../accessors/PositionNode.js'; +import { positionLocal, positionView } from '../accessors/PositionNode.js'; import { skinning } from '../accessors/SkinningNode.js'; +import { morph } from '../accessors/MorphNode.js'; import { texture } from '../accessors/TextureNode.js'; import { cubeTexture } from '../accessors/CubeTextureNode.js'; import { lightsWithoutWrap } from '../lighting/LightsNode.js'; -import { mix } from '../math/MathNode.js'; +import { mix, dFdx, dFdy } from '../math/MathNode.js'; import { float, vec3, vec4 } from '../shadernode/ShaderNode.js'; import AONode from '../lighting/AONode.js'; import EnvironmentNode from '../lighting/EnvironmentNode.js'; @@ -99,9 +100,16 @@ class NodeMaterial extends ShaderMaterial { constructPosition( builder ) { const object = builder.object; + const geometry = object.geometry; builder.addStack(); + if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) { + + builder.stack.add( morph( object ) ); + + } + if ( object.isSkinnedMesh === true ) { builder.stack.add( skinning( object ) ); @@ -169,11 +177,21 @@ class NodeMaterial extends ShaderMaterial { // NORMAL VIEW - const normalNode = this.normalNode ? vec3( this.normalNode ) : materialNormal; + if ( this.flatShading === true ) { + + const fdx = dFdx( positionView ); + const fdy = dFdy( positionView.negate() ); // use -positionView ? + const normalNode = fdx.cross( fdy ).normalize(); - stack.assign( transformedNormalView, normalNode ); + stack.assign( transformedNormalView, normalNode ); - return normalNode; + } else { + + const normalNode = this.normalNode ? vec3( this.normalNode ) : materialNormal; + + stack.assign( transformedNormalView, normalNode ); + + } } diff --git a/examples/jsm/renderers/common/Background.js b/examples/jsm/renderers/common/Background.js index a435284d6c9f52..2a284982130f5b 100644 --- a/examples/jsm/renderers/common/Background.js +++ b/examples/jsm/renderers/common/Background.js @@ -30,14 +30,14 @@ class Background extends DataMap { // no background settings, use clear color configuration from the renderer - _clearColor.copy( renderer._clearColor ); + _clearColor.copyLinearToSRGB( renderer._clearColor ); _clearAlpha = renderer._clearAlpha; } else if ( background.isColor === true ) { // background is an opaque color - _clearColor.copy( background ); + _clearColor.copyLinearToSRGB( background ); _clearAlpha = 1; forceClear = true; diff --git a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js index 91d0b32db073a5..18a396be469fd8 100644 --- a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js @@ -160,6 +160,12 @@ class GLSLNodeBuilder extends NodeBuilder { } + getVertexIndex() { + + return 'gl_VertexID'; + + } + getFrontFacing() { return 'gl_FrontFacing'; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index 4b8e10d3c5c848..9bb71b0789ceda 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -573,6 +573,12 @@ class WebGLNodeBuilder extends NodeBuilder { } + getVertexIndex() { + + return 'gl_VertexID'; + + } + getFrontFacing() { return 'gl_FrontFacing'; diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index b4f0862ad35e0f..717c0bcaf4b347 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -400,6 +400,18 @@ class WGSLNodeBuilder extends NodeBuilder { } + getVertexIndex() { + + if ( this.shaderStage === 'vertex' ) { + + return this.getBuiltin( 'vertex_index', 'vertexIndex', 'u32', 'attribute' ); + + } + + return 'vertexIndex'; + + } + getInstanceIndex() { if ( this.shaderStage === 'vertex' ) { diff --git a/examples/screenshots/webgpu_morphtargets.jpg b/examples/screenshots/webgpu_morphtargets.jpg new file mode 100644 index 0000000000000000000000000000000000000000..001fe203e0b94e476d12e5c1c76aa979a0fbc77f GIT binary patch literal 3413 zcmex=iF;N$`UAd82aiwDGkXk%h!W@hDLXJZFTlSKSKz#z!M@QZN*Gov5_lOQ9r zAmjfdjEjJ7WCc47=uik?WMXDvWn%|Afm;CRY-VPlV_AVN1*)tC$}zAAvI;30I#U-U>$dGXcJ4ZK_{h;?$4{I*b?NeztJkjIxOwa0qsLF4K70P+<*SdMK7aZ8?fZ|P zzZe;qA>IL!82$lzoRJ9>=IkK_0Zp(e6?1osf!KmTtr@Gvt1BaB&)!Jgq?{R@T#ObuKN n8l%)`5R9gR(Tp%!7K|2$qcy^4Z8%yRj@E|5u{QkA@c$+N?bpn5 literal 0 HcmV?d00001 diff --git a/examples/webgpu_morphtargets.html b/examples/webgpu_morphtargets.html new file mode 100644 index 00000000000000..d858f60eea1642 --- /dev/null +++ b/examples/webgpu_morphtargets.html @@ -0,0 +1,182 @@ + + + + three.js webgpu - morph targets + + + + + + +
+
+ three.js - morph targets
+ by Discover three.js +
+ + + + + + + + + + + diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index 17680a7b31bed4..78488c577d9a30 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -123,6 +123,7 @@ const exceptionList = [ 'webgpu_loader_gltf_compressed', 'webgpu_materials', 'webgpu_materials_video', + 'webgpu_morphtargets', 'webgpu_particles', 'webgpu_rtt', 'webgpu_sandbox',