Преглед изворни кода

WebGPURenderer: InstancedMesh Supports Instance Color and Morph Target (#27928)

* add instancing and some fix/feat

* update example for webgpu

* add example to files.json

* fix skinning webgpu

* cleanup example and pup

* up shadow

* add default normal instead
Renaud Rohlinger пре 1 година
родитељ
комит
b0a6ea1271

+ 2 - 1
examples/files.json

@@ -382,7 +382,8 @@
 		"webgpu_mirror",
 		"webgpu_multisampled_renderbuffers",
 		"webgpu_materials_texture_anisotropy",
-		"webgpu_storage_buffer"
+		"webgpu_storage_buffer",
+		"webgpu_instancing_morph"
 	],
 	"webaudio": [
 		"webaudio_orientation",

+ 25 - 2
examples/jsm/nodes/accessors/InstanceNode.js

@@ -1,9 +1,10 @@
 import Node, { addNodeClass } from '../core/Node.js';
+import { varyingProperty } from '../core/PropertyNode.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';
-import { DynamicDrawUsage, InstancedInterleavedBuffer } from 'three';
+import { DynamicDrawUsage, InstancedInterleavedBuffer, InstancedBufferAttribute } from 'three';
 
 class InstanceNode extends Node {
 
@@ -15,15 +16,18 @@ class InstanceNode extends Node {
 
 		this.instanceMatrixNode = null;
 
+		this.instanceColorNode = null;
+
 	}
 
 	setup( /*builder*/ ) {
 
 		let instanceMatrixNode = this.instanceMatrixNode;
 
+		const instanceMesh = this.instanceMesh;
+
 		if ( instanceMatrixNode === null ) {
 
-			const instanceMesh = this.instanceMesh;
 			const instanceAttribute = instanceMesh.instanceMatrix;
 			const buffer = new InstancedInterleavedBuffer( instanceAttribute.array, 16, 1 );
 
@@ -43,6 +47,17 @@ class InstanceNode extends Node {
 
 		}
 
+		const instanceColorAttribute = instanceMesh.instanceColor;
+
+		if ( instanceColorAttribute && this.instanceColorNode === null ) {
+
+			const buffer = new InstancedBufferAttribute( instanceColorAttribute.array, 3 );
+			const bufferFn = instanceColorAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;
+
+			this.instanceColorNode = vec3( bufferFn( buffer, 'vec3', 3, 0 ) );
+
+		}
+
 		// POSITION
 
 		const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;
@@ -60,6 +75,14 @@ class InstanceNode extends Node {
 		positionLocal.assign( instancePosition );
 		normalLocal.assign( instanceNormal );
 
+		// COLOR
+
+		if ( this.instanceColorNode !== null ) {
+
+			varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode );
+
+		}
+
 	}
 
 }

+ 13 - 3
examples/jsm/nodes/accessors/MorphNode.js

@@ -1,12 +1,12 @@
 import Node, { addNodeClass } from '../core/Node.js';
 import { NodeUpdateType } from '../core/constants.js';
-import { nodeProxy, tslFn } from '../shadernode/ShaderNode.js';
+import { float, nodeProxy, tslFn } from '../shadernode/ShaderNode.js';
 import { uniform } from '../core/UniformNode.js';
 import { reference } from './ReferenceNode.js';
 import { positionLocal } from './PositionNode.js';
 import { normalLocal } from './NormalNode.js';
 import { textureLoad } from './TextureNode.js';
-import { vertexIndex } from '../core/IndexNode.js';
+import { instanceIndex, vertexIndex } from '../core/IndexNode.js';
 import { ivec2, int } from '../shadernode/ShaderNode.js';
 import { DataArrayTexture, Vector2, Vector4, FloatType } from 'three';
 import { loop } from '../utils/LoopNode.js';
@@ -188,7 +188,17 @@ class MorphNode extends Node {
 
 		loop( morphTargetsCount, ( { i } ) => {
 
-			const influence = reference( 'morphTargetInfluences', 'float' ).element( i );
+			const influence = float( 0 ).toVar();
+
+			if ( this.mesh.isInstancedMesh === true && this.mesh.morphTexture !== null ) {
+
+				influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r );
+
+			} else {
+
+				influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() );
+
+			}
 
 			if ( hasMorphPosition === true ) {
 

+ 12 - 2
examples/jsm/nodes/accessors/NormalNode.js

@@ -5,7 +5,7 @@ import { property } from '../core/PropertyNode.js';
 import { normalize } from '../math/MathNode.js';
 import { cameraViewMatrix } from './CameraNode.js';
 import { modelNormalMatrix } from './ModelNode.js';
-import { nodeImmutable } from '../shadernode/ShaderNode.js';
+import { nodeImmutable, vec3 } from '../shadernode/ShaderNode.js';
 
 class NormalNode extends Node {
 
@@ -37,7 +37,17 @@ class NormalNode extends Node {
 
 		if ( scope === NormalNode.GEOMETRY ) {
 
-			outputNode = attribute( 'normal', 'vec3' );
+			const geometryAttribute = builder.hasGeometryAttribute( 'normal' );
+
+			if ( geometryAttribute === false ) {
+
+				outputNode = vec3( 0, 1, 0 );
+
+			} else {
+
+				outputNode = attribute( 'normal', 'vec3' );
+
+			}
 
 		} else if ( scope === NormalNode.LOCAL ) {
 

+ 12 - 2
examples/jsm/nodes/materials/NodeMaterial.js

@@ -1,7 +1,7 @@
 import { Material, ShaderMaterial, NoColorSpace, LinearSRGBColorSpace } from 'three';
 import { getNodeChildren, getCacheKey } from '../core/NodeUtils.js';
 import { attribute } from '../core/AttributeNode.js';
-import { output, diffuseColor } from '../core/PropertyNode.js';
+import { output, diffuseColor, varyingProperty } from '../core/PropertyNode.js';
 import { materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal } from '../accessors/MaterialNode.js';
 import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js';
 import { transformedNormalView } from '../accessors/NormalNode.js';
@@ -224,7 +224,7 @@ class NodeMaterial extends ShaderMaterial {
 
 	}
 
-	setupDiffuseColor( { geometry } ) {
+	setupDiffuseColor( { object, geometry } ) {
 
 		let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;
 
@@ -236,6 +236,16 @@ class NodeMaterial extends ShaderMaterial {
 
 		}
 
+		// Instanced colors
+
+		if ( object.instanceColor ) {
+
+			const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' );
+
+			colorNode = instanceColor.mul( colorNode );
+
+		}
+
 		// COLOR
 
 		diffuseColor.assign( colorNode );

BIN
examples/screenshots/webgpu_instancing_morph.jpg


+ 195 - 0
examples/webgpu_instancing_morph.html

@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - instancing - Morph Target Animations</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+			import { MeshStandardNodeMaterial } from 'three/nodes';
+
+			let camera, scene, renderer, stats, mesh, mixer, dummy;
+
+			const offset = 5000;
+
+			const timeOffsets = new Float32Array( 1024 );
+
+			for ( let i = 0; i < 1024; i ++ ) {
+
+				timeOffsets[ i ] = Math.random() * 3;
+
+			}
+
+			const clock = new THREE.Clock( true );
+
+			init();
+
+			function init() {
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 100, 10000 );
+
+				scene = new THREE.Scene();
+
+				scene.background = new THREE.Color( 0x99DDFF );
+
+				scene.fog = new THREE.Fog( 0x99DDFF, 5000, 10000 );
+
+
+				//
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				const light = new THREE.DirectionalLight( 0xffffff, 1 );
+
+				light.position.set( 200, 1000, 50 );
+
+				light.shadow.mapSize.width = 2048;
+				light.shadow.mapSize.height = 2048;
+				light.castShadow = true;
+
+				light.shadow.camera.left = - 5000;
+				light.shadow.camera.right = 5000;
+				light.shadow.camera.top = 5000;
+				light.shadow.camera.bottom = - 5000;
+				light.shadow.camera.far = 2000;
+
+				light.shadow.bias = - 0.01;
+
+				light.shadow.camera.updateProjectionMatrix();
+
+				scene.add( light );
+
+				const hemi = new THREE.HemisphereLight( 0x99DDFF, 0x669933, 1 / 3 );
+
+				scene.add( hemi );
+
+				const ground = new THREE.Mesh(
+					new THREE.PlaneGeometry( 1000000, 1000000 ),
+					new THREE.MeshStandardMaterial( { color: 0x669933 } )
+				);
+
+				ground.rotation.x = - Math.PI / 2;
+
+				ground.receiveShadow = true;
+
+				scene.add( ground );
+
+				const loader = new GLTFLoader();
+
+				loader.load( 'models/gltf/Horse.glb', function ( glb ) {
+
+					dummy = glb.scene.children[ 0 ];
+
+					mesh = new THREE.InstancedMesh( dummy.geometry, new MeshStandardNodeMaterial( {
+						flatShading: true,
+					} ), 1024 );
+
+					mesh.castShadow = true;
+
+					for ( let x = 0, i = 0; x < 32; x ++ ) {
+
+						for ( let y = 0; y < 32; y ++ ) {
+
+							dummy.position.set( offset - 300 * x + 200 * Math.random(), 0, offset - 300 * y );
+
+							dummy.updateMatrix();
+
+							mesh.setMatrixAt( i, dummy.matrix );
+
+							mesh.setColorAt( i, new THREE.Color( `hsl(${Math.random() * 360}, 50%, 66%)` ) );
+
+							i ++;
+			
+						}
+
+			
+					}
+
+					scene.add( mesh );
+
+					mixer = new THREE.AnimationMixer( glb.scene );
+
+					const action = mixer.clipAction( glb.animations[ 0 ] );
+
+					action.play();
+			
+				} );
+
+
+				// renderer
+
+				renderer = new WebGPURenderer( { antialias: true } );
+
+				renderer.setAnimationLoop( animate );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				const time = clock.getElapsedTime();
+
+				const r = 3000;
+				camera.position.set( Math.sin( time / 10 ) * r, 1500 + 1000 * Math.cos( time / 5 ), Math.cos( time / 10 ) * r );
+				camera.lookAt( 0, 0, 0 );
+
+				if ( mesh ) {
+
+					for ( let i = 0; i < 1024; i ++ ) {
+
+						mixer.setTime( time + timeOffsets[ i ] );
+
+						mesh.setMorphAt( i, dummy );
+
+					}
+
+					mesh.morphTexture.needsUpdate = true;
+
+				}
+
+				renderer.render( scene, camera );
+
+				stats.update();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -139,6 +139,7 @@ const exceptionList = [
 	'webgpu_tsl_transpiler',
 	'webgpu_portal',
 	'webgpu_custom_fog',
+	'webgpu_instancing_morph',
 
 	// WebGPU idleTime and parseTime too low
 	'webgpu_compute_particles',