Sfoglia il codice sorgente

InstancedMesh: Add support for morph targets. (#27669)

* Pack influences for all instances in a texture

* better check

* handle shader cache

* Update WebGLProgram.js

Clean up.

* morphtarget_instancing -> morphinstance_vertex

---------

Co-authored-by: Michael Herzog <[email protected]>
Yannis Gravezas 1 anno fa
parent
commit
03470ddb32

+ 1 - 0
examples/files.json

@@ -44,6 +44,7 @@
 		"webgl_geometry_text_shapes",
 		"webgl_geometry_text_stroke",
 		"webgl_helpers",
+		"webgl_instancing_morph",
 		"webgl_instancing_dynamic",
 		"webgl_instancing_performance",
 		"webgl_instancing_raycast",

BIN
examples/screenshots/webgl_instancing_morph.jpg


+ 195 - 0
examples/webgl_instancing_morph.html

@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - 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/"
+				}
+			}
+		</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';
+			
+			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();
+			animate();
+
+			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 );
+
+				const light = new THREE.DirectionalLight( 0xffffff, 1 );
+
+				light.position.set( 200, 1000, 50 );
+
+				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, depthWrite: true } )
+				);
+
+				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, dummy.material, 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 = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.VSMShadowMap;
+				//
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+
+				stats.update();
+
+			}
+
+			function render() {
+
+				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 );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 35 - 0
src/objects/InstancedMesh.js

@@ -3,6 +3,8 @@ import { Mesh } from './Mesh.js';
 import { Box3 } from '../math/Box3.js';
 import { Matrix4 } from '../math/Matrix4.js';
 import { Sphere } from '../math/Sphere.js';
+import { DataTexture } from '../textures/DataTexture.js';
+import { FloatType, RedFormat } from '../constants.js';
 
 const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4();
 const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4();
@@ -24,6 +26,7 @@ class InstancedMesh extends Mesh {
 
 		this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );
 		this.instanceColor = null;
+		this.morphTexture = null;
 
 		this.count = count;
 
@@ -199,6 +202,38 @@ class InstancedMesh extends Mesh {
 
 	}
 
+	setMorphAt( index, dummy ) {
+
+		const objectInfluences = dummy.morphTargetInfluences;
+
+		const len = objectInfluences.length + 1; // morphBaseInfluence + all influences
+
+		if ( this.morphTexture === null ) {
+
+			this.morphTexture = new DataTexture( new Float32Array( len * this.count ), len, this.count, RedFormat, FloatType );
+
+		}
+
+		const array = this.morphTexture.source.data.data;
+
+		let morphInfluencesSum = 0;
+
+		for ( let i = 0; i < objectInfluences.length; i ++ ) {
+
+			morphInfluencesSum += objectInfluences[ i ];
+
+		}
+
+		const morphBaseInfluence = this.geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
+
+		const dataIndex = len * index;
+
+		array[ dataIndex ] = morphBaseInfluence;
+
+		array.set( objectInfluences, dataIndex + 1 );
+
+	}
+
 	updateMorphTargets() {
 
 	}

+ 9 - 0
src/renderers/WebGLRenderer.js

@@ -1724,6 +1724,7 @@ class WebGLRenderer {
 			materialProperties.batching = parameters.batching;
 			materialProperties.instancing = parameters.instancing;
 			materialProperties.instancingColor = parameters.instancingColor;
+			materialProperties.instancingMorph = parameters.instancingMorph;
 			materialProperties.skinning = parameters.skinning;
 			materialProperties.morphTargets = parameters.morphTargets;
 			materialProperties.morphNormals = parameters.morphNormals;
@@ -1834,6 +1835,14 @@ class WebGLRenderer {
 
 					needsProgramChange = true;
 
+				} else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) {
+
+					needsProgramChange = true;
+
+				} else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) {
+
+					needsProgramChange = true;
+
 				} else if ( materialProperties.envMap !== envMap ) {
 
 					needsProgramChange = true;

+ 2 - 0
src/renderers/shaders/ShaderChunk.js

@@ -65,6 +65,7 @@ import map_particle_fragment from './ShaderChunk/map_particle_fragment.glsl.js';
 import map_particle_pars_fragment from './ShaderChunk/map_particle_pars_fragment.glsl.js';
 import metalnessmap_fragment from './ShaderChunk/metalnessmap_fragment.glsl.js';
 import metalnessmap_pars_fragment from './ShaderChunk/metalnessmap_pars_fragment.glsl.js';
+import morphinstance_vertex from './ShaderChunk/morphinstance_vertex.glsl.js';
 import morphcolor_vertex from './ShaderChunk/morphcolor_vertex.glsl.js';
 import morphnormal_vertex from './ShaderChunk/morphnormal_vertex.glsl.js';
 import morphtarget_pars_vertex from './ShaderChunk/morphtarget_pars_vertex.glsl.js';
@@ -192,6 +193,7 @@ export const ShaderChunk = {
 	map_particle_pars_fragment: map_particle_pars_fragment,
 	metalnessmap_fragment: metalnessmap_fragment,
 	metalnessmap_pars_fragment: metalnessmap_pars_fragment,
+	morphinstance_vertex: morphinstance_vertex,
 	morphcolor_vertex: morphcolor_vertex,
 	morphnormal_vertex: morphnormal_vertex,
 	morphtarget_pars_vertex: morphtarget_pars_vertex,

+ 14 - 0
src/renderers/shaders/ShaderChunk/morphinstance_vertex.glsl.js

@@ -0,0 +1,14 @@
+export default /* glsl */`
+#ifdef USE_INSTANCING_MORPH
+
+	float morphTargetInfluences[MORPHTARGETS_COUNT];
+
+	float morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;
+
+	for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {
+
+		morphTargetInfluences[i] =  texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;
+
+	}
+#endif
+`;

+ 11 - 2
src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl.js

@@ -1,11 +1,20 @@
 export default /* glsl */`
 #ifdef USE_MORPHTARGETS
 
-	uniform float morphTargetBaseInfluence;
+	#ifndef USE_INSTANCING_MORPH
+
+		uniform float morphTargetBaseInfluence;
+
+	#endif
 
 	#ifdef MORPHTARGETS_TEXTURE
 
-		uniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];
+		#ifndef USE_INSTANCING_MORPH
+
+			uniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];
+
+		#endif
+
 		uniform sampler2DArray morphTargetsTexture;
 		uniform ivec2 morphTargetsTextureSize;
 

+ 2 - 0
src/renderers/shaders/ShaderLib/depth.glsl.js

@@ -20,6 +20,8 @@ void main() {
 	#include <batching_vertex>
 	#include <skinbase_vertex>
 
+	#include <morphinstance_vertex>
+
 	#ifdef USE_DISPLACEMENTMAP
 
 		#include <beginnormal_vertex>

+ 2 - 0
src/renderers/shaders/ShaderLib/distanceRGBA.glsl.js

@@ -18,6 +18,8 @@ void main() {
 	#include <batching_vertex>
 	#include <skinbase_vertex>
 
+	#include <morphinstance_vertex>
+
 	#ifdef USE_DISPLACEMENTMAP
 
 		#include <beginnormal_vertex>

+ 1 - 0
src/renderers/shaders/ShaderLib/linedashed.glsl.js

@@ -18,6 +18,7 @@ void main() {
 
 	#include <uv_vertex>
 	#include <color_vertex>
+	#include <morphinstance_vertex>
 	#include <morphcolor_vertex>
 	#include <begin_vertex>
 	#include <morphtarget_vertex>

+ 1 - 0
src/renderers/shaders/ShaderLib/meshbasic.glsl.js

@@ -14,6 +14,7 @@ void main() {
 
 	#include <uv_vertex>
 	#include <color_vertex>
+	#include <morphinstance_vertex>
 	#include <morphcolor_vertex>
 	#include <batching_vertex>
 

+ 1 - 0
src/renderers/shaders/ShaderLib/meshlambert.glsl.js

@@ -21,6 +21,7 @@ void main() {
 
 	#include <uv_vertex>
 	#include <color_vertex>
+	#include <morphinstance_vertex>
 	#include <morphcolor_vertex>
 	#include <batching_vertex>
 

+ 1 - 0
src/renderers/shaders/ShaderLib/meshmatcap.glsl.js

@@ -20,6 +20,7 @@ void main() {
 
 	#include <uv_vertex>
 	#include <color_vertex>
+	#include <morphinstance_vertex>
 	#include <morphcolor_vertex>
 	#include <batching_vertex>
 

+ 1 - 0
src/renderers/shaders/ShaderLib/meshnormal.glsl.js

@@ -23,6 +23,7 @@ void main() {
 	#include <batching_vertex>
 
 	#include <beginnormal_vertex>
+	#include <morphinstance_vertex>
 	#include <morphnormal_vertex>
 	#include <skinbase_vertex>
 	#include <skinnormal_vertex>

+ 1 - 0
src/renderers/shaders/ShaderLib/meshphong.glsl.js

@@ -25,6 +25,7 @@ void main() {
 	#include <batching_vertex>
 
 	#include <beginnormal_vertex>
+	#include <morphinstance_vertex>
 	#include <morphnormal_vertex>
 	#include <skinbase_vertex>
 	#include <skinnormal_vertex>

+ 1 - 0
src/renderers/shaders/ShaderLib/meshphysical.glsl.js

@@ -26,6 +26,7 @@ void main() {
 
 	#include <uv_vertex>
 	#include <color_vertex>
+	#include <morphinstance_vertex>
 	#include <morphcolor_vertex>
 	#include <batching_vertex>
 

+ 1 - 0
src/renderers/shaders/ShaderLib/meshtoon.glsl.js

@@ -20,6 +20,7 @@ void main() {
 
 	#include <uv_vertex>
 	#include <color_vertex>
+	#include <morphinstance_vertex>
 	#include <morphcolor_vertex>
 	#include <batching_vertex>
 

+ 1 - 0
src/renderers/shaders/ShaderLib/points.glsl.js

@@ -25,6 +25,7 @@ void main() {
 	#endif
 
 	#include <color_vertex>
+	#include <morphinstance_vertex>
 	#include <morphcolor_vertex>
 	#include <begin_vertex>
 	#include <morphtarget_vertex>

+ 1 - 0
src/renderers/shaders/ShaderLib/shadow.glsl.js

@@ -12,6 +12,7 @@ void main() {
 	#include <batching_vertex>
 
 	#include <beginnormal_vertex>
+	#include <morphinstance_vertex>
 	#include <morphnormal_vertex>
 	#include <skinbase_vertex>
 	#include <skinnormal_vertex>

+ 15 - 8
src/renderers/webgl/WebGLMorphtargets.js

@@ -154,24 +154,31 @@ function WebGLMorphtargets( gl, capabilities, textures ) {
 			}
 
 			//
+			if ( object.isInstancedMesh === true && object.morphTexture !== null ) {
 
-			let morphInfluencesSum = 0;
+				program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures );
 
-			for ( let i = 0; i < objectInfluences.length; i ++ ) {
+			} else {
 
-				morphInfluencesSum += objectInfluences[ i ];
+				let morphInfluencesSum = 0;
 
-			}
+				for ( let i = 0; i < objectInfluences.length; i ++ ) {
 
-			const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
+					morphInfluencesSum += objectInfluences[ i ];
 
-			program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
-			program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences );
+				}
+
+				const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
+
+
+				program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
+				program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences );
+
+			}
 
 			program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures );
 			program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size );
 
-
 		} else {
 
 			// When object doesn't have morph target influences defined, we treat it as a 0-length array

+ 7 - 0
src/renderers/webgl/WebGLProgram.js

@@ -547,6 +547,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
 			parameters.batching ? '#define USE_BATCHING' : '',
 			parameters.instancing ? '#define USE_INSTANCING' : '',
 			parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',
+			parameters.instancingMorph ? '#define USE_INSTANCING_MORPH' : '',
 
 			parameters.useFog && parameters.fog ? '#define USE_FOG' : '',
 			parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '',
@@ -678,6 +679,12 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
 
 			'#endif',
 
+			'#ifdef USE_INSTANCING_MORPH',
+
+			'	uniform sampler2D morphTexture;',
+
+			'#endif',
+
 			'attribute vec3 position;',
 			'attribute vec3 normal;',
 			'attribute vec2 uv;',

+ 19 - 16
src/renderers/webgl/WebGLPrograms.js

@@ -196,6 +196,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			batching: IS_BATCHEDMESH,
 			instancing: IS_INSTANCEDMESH,
 			instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null,
+			instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null,
 
 			supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES,
 			outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ),
@@ -482,38 +483,40 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			_programLayers.enable( 2 );
 		if ( parameters.instancingColor )
 			_programLayers.enable( 3 );
-		if ( parameters.matcap )
+		if ( parameters.instancingMorph )
 			_programLayers.enable( 4 );
-		if ( parameters.envMap )
+		if ( parameters.matcap )
 			_programLayers.enable( 5 );
-		if ( parameters.normalMapObjectSpace )
+		if ( parameters.envMap )
 			_programLayers.enable( 6 );
-		if ( parameters.normalMapTangentSpace )
+		if ( parameters.normalMapObjectSpace )
 			_programLayers.enable( 7 );
-		if ( parameters.clearcoat )
+		if ( parameters.normalMapTangentSpace )
 			_programLayers.enable( 8 );
-		if ( parameters.iridescence )
+		if ( parameters.clearcoat )
 			_programLayers.enable( 9 );
-		if ( parameters.alphaTest )
+		if ( parameters.iridescence )
 			_programLayers.enable( 10 );
-		if ( parameters.vertexColors )
+		if ( parameters.alphaTest )
 			_programLayers.enable( 11 );
-		if ( parameters.vertexAlphas )
+		if ( parameters.vertexColors )
 			_programLayers.enable( 12 );
-		if ( parameters.vertexUv1s )
+		if ( parameters.vertexAlphas )
 			_programLayers.enable( 13 );
-		if ( parameters.vertexUv2s )
+		if ( parameters.vertexUv1s )
 			_programLayers.enable( 14 );
-		if ( parameters.vertexUv3s )
+		if ( parameters.vertexUv2s )
 			_programLayers.enable( 15 );
-		if ( parameters.vertexTangents )
+		if ( parameters.vertexUv3s )
 			_programLayers.enable( 16 );
-		if ( parameters.anisotropy )
+		if ( parameters.vertexTangents )
 			_programLayers.enable( 17 );
-		if ( parameters.alphaHash )
+		if ( parameters.anisotropy )
 			_programLayers.enable( 18 );
-		if ( parameters.batching )
+		if ( parameters.alphaHash )
 			_programLayers.enable( 19 );
+		if ( parameters.batching )
+			_programLayers.enable( 20 );
 
 		array.push( _programLayers.mask );
 		_programLayers.disableAll();