浏览代码

Examples: Simplify webgl_animation_multiple. (#23066)

Michael Herzog 3 年之前
父节点
当前提交
95b5a61192
共有 2 个文件被更改,包括 65 次插入321 次删除
  1. 二进制
      examples/screenshots/webgl_animation_multiple.jpg
  2. 65 321
      examples/webgl_animation_multiple.html

二进制
examples/screenshots/webgl_animation_multiple.jpg


+ 65 - 321
examples/webgl_animation_multiple.html

@@ -1,16 +1,14 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<title>Multiple animated objects</title>
+		<title>Multiple animated skinned meshes</title>
 		<meta charset="utf-8">
 		<meta charset="utf-8">
 		<meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport">
 		<meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport">
 		<link type="text/css" rel="stylesheet" href="main.css">
 		<link type="text/css" rel="stylesheet" href="main.css">
 	</head>
 	</head>
 	<body>
 	<body>
-		<div id="container"></div>
-
 		<div id="info">
 		<div id="info">
-			This demo shows how to clone a skinned 3d model using <strong>SkeletonUtils.clone()</strong><br/>
+			This demo shows how to clone a skinned mesh using <strong>SkeletonUtils.clone()</strong><br/>
 			Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
 			Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
 		</div>
 		</div>
 
 
@@ -21,365 +19,111 @@
 			import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
 			import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
 			import * as SkeletonUtils from './jsm/utils/SkeletonUtils.js';
 			import * as SkeletonUtils from './jsm/utils/SkeletonUtils.js';
 
 
-			//////////////////////////////
-			// Global objects
-			//////////////////////////////
-			let worldScene = null; // THREE.Scene where it all will be rendered
-			let renderer = null;
-			let camera = null;
-			let clock = null;
-			const mixers = []; // All the THREE.AnimationMixer objects for all the animations in the scene
-			//////////////////////////////
-
-
-			//////////////////////////////
-			// Information about our 3D models and units
-			//////////////////////////////
-
-			// The names of the 3D models to load. One-per file.
-			// A model may have multiple SkinnedMesh objects as well as several rigs (armatures). Units will define which
-			// meshes, armatures and animations to use. We will load the whole scene for each object and clone it for each unit.
-			// Models are from https://www.mixamo.com/
-			const MODELS = [
-				{ name: "Soldier" },
-				{ name: "Parrot" },
-			];
-
-			// Here we define instances of the models that we want to place in the scene, their position, scale and the animations
-			// that must be played.
-			const UNITS = [
-				{
-					modelName: "Soldier", // Will use the 3D model from file models/gltf/Soldier.glb
-					meshName: "vanguard_Mesh", // Name of the main mesh to animate
-					position: { x: 0, y: 0, z: 0 }, // Where to put the unit in the scene
-					scale: 1, // Scaling of the unit. 1.0 means: use original size, 0.1 means "10 times smaller", etc.
-					animationName: "Idle" // Name of animation to run
-				},
-				{
-					modelName: "Soldier",
-					meshName: "vanguard_Mesh",
-					position: { x: 3, y: 0, z: 0 },
-					scale: 2,
-					animationName: "Walk"
-				},
-				{
-					modelName: "Soldier",
-					meshName: "vanguard_Mesh",
-					position: { x: 1, y: 0, z: 0 },
-					scale: 1,
-					animationName: "Run"
-				},
-				{
-					modelName: "Parrot",
-					meshName: "mesh_0",
-					position: { x: - 4, y: 0, z: 0 },
-					rotation: { x: 0, y: Math.PI, z: 0 },
-					scale: 0.01,
-					animationName: "parrot_A_"
-				},
-				{
-					modelName: "Parrot",
-					meshName: "mesh_0",
-					position: { x: - 2, y: 0, z: 0 },
-					rotation: { x: 0, y: Math.PI / 2, z: 0 },
-					scale: 0.02,
-					animationName: null
-				},
-			];
-
-			//////////////////////////////
-			// The main setup happens here
-			//////////////////////////////
-			let numLoadedModels = 0;
-			initScene();
-			initRenderer();
-			loadModels();
-			animate();
-			//////////////////////////////
-
-
-			//////////////////////////////
-			// Function implementations
-			//////////////////////////////
-			/**
-			 * Function that starts loading process for the next model in the queue. The loading process is
-			 * asynchronous: it happens "in the background". Therefore we don't load all the models at once. We load one,
-			 * wait until it is done, then load the next one. When all models are loaded, we call loadUnits().
-			 */
-			function loadModels() {
-
-				for ( let i = 0; i < MODELS.length; ++ i ) {
-
-					const m = MODELS[ i ];
-
-					loadGltfModel( m, function () {
-
-						++ numLoadedModels;
-
-						if ( numLoadedModels === MODELS.length ) {
-
-							console.log( "All models loaded, time to instantiate units..." );
-							instantiateUnits();
-
-						}
-
-					} );
-
-				}
-
-			}
-
-			/**
-			 * Look at UNITS configuration, clone necessary 3D model scenes, place the armatures and meshes in the scene and
-			 * launch necessary animations
-			 */
-			function instantiateUnits() {
-
-				let numSuccess = 0;
-
-				for ( let i = 0; i < UNITS.length; ++ i ) {
-
-					const u = UNITS[ i ];
-					const model = getModelByName( u.modelName );
-
-					if ( model ) {
-
-						const clonedScene = SkeletonUtils.clone( model.scene );
-
-						if ( clonedScene ) {
-
-							// THREE.Scene is cloned properly, let's find one mesh and launch animation for it
-							const clonedMesh = clonedScene.getObjectByName( u.meshName );
-
-							if ( clonedMesh ) {
-
-								const mixer = startAnimation( clonedMesh, model.animations, u.animationName );
-
-								// Save the animation mixer in the list, will need it in the animation loop
-								mixers.push( mixer );
-								numSuccess ++;
-
-							}
+			let camera, scene, renderer;
+			let clock;
 
 
-							// Different models can have different configurations of armatures and meshes. Therefore,
-							// We can't set position, scale or rotation to individual mesh objects. Instead we set
-							// it to the whole cloned scene and then add the whole scene to the game world
-							// Note: this may have weird effects if you have lights or other items in the GLTF file's scene!
-							worldScene.add( clonedScene );
+			const mixers = [];
 
 
-							if ( u.position ) {
-
-								clonedScene.position.set( u.position.x, u.position.y, u.position.z );
-
-							}
-
-							if ( u.scale ) {
-
-								clonedScene.scale.set( u.scale, u.scale, u.scale );
-
-							}
-
-							if ( u.rotation ) {
-
-								clonedScene.rotation.x = u.rotation.x;
-								clonedScene.rotation.y = u.rotation.y;
-								clonedScene.rotation.z = u.rotation.z;
-
-							}
-
-					        }
-
-					} else {
-
-						console.error( "Can not find model", u.modelName );
-
-					}
-
-				}
-
-				console.log( `Successfully instantiated ${numSuccess} units` );
-
-			}
-
-			/**
-			 * Start animation for a specific mesh object. Find the animation by name in the 3D model's animation array
-			 * @param skinnedMesh {THREE.SkinnedMesh} The mesh to animate
-			 * @param animations {Array} Array containing all the animations for this model
-			 * @param animationName {string} Name of the animation to launch
-			 * @return {THREE.AnimationMixer} Mixer to be used in the render loop
-			 */
-			function startAnimation( skinnedMesh, animations, animationName ) {
-
-				const mixer = new THREE.AnimationMixer( skinnedMesh );
-				const clip = THREE.AnimationClip.findByName( animations, animationName );
-
-				if ( clip ) {
-
-					const action = mixer.clipAction( clip );
-					action.play();
-
-				}
-
-				return mixer;
-
-			}
+			init();
+			animate();
 
 
-			/**
-			 * Find a model object by name
-			 * @param name
-			 * @returns {object|null}
-			 */
-			function getModelByName( name ) {
+			function init() {
 
 
-				for ( let i = 0; i < MODELS.length; ++ i ) {
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( 2, 3, - 6 );
+				camera.lookAt( 0, 1, 0 );
 
 
-					if ( MODELS[ i ].name === name ) {
+				clock = new THREE.Clock();
 
 
-						return MODELS[ i ];
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xa0a0a0 );
+				scene.fog = new THREE.Fog( 0xa0a0a0, 10, 50 );
 
 
-					}
+				const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
+				hemiLight.position.set( 0, 20, 0 );
+				scene.add( hemiLight );
 
 
-				}
+				const dirLight = new THREE.DirectionalLight( 0xffffff );
+				dirLight.position.set( - 3, 10, - 10 );
+				dirLight.castShadow = true;
+				dirLight.shadow.camera.top = 4;
+				dirLight.shadow.camera.bottom = - 4;
+				dirLight.shadow.camera.left = - 4;
+				dirLight.shadow.camera.right = 4;
+				dirLight.shadow.camera.near = 0.1;
+				dirLight.shadow.camera.far = 40;
+				scene.add( dirLight );
 
 
-				return null;
+				// scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );
 
 
-			}
+				// ground
 
 
-			/**
-			 * Load a 3D model from a GLTF file. Use the GLTFLoader.
-			 * @param model {object} Model config, one item from the MODELS array. It will be updated inside the function!
-			 * @param onLoaded {function} A callback function that will be called when the model is loaded
-			 */
-			function loadGltfModel( model, onLoaded ) {
+				const mesh = new THREE.Mesh( new THREE.PlaneGeometry( 200, 200 ), new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) );
+				mesh.rotation.x = - Math.PI / 2;
+				mesh.receiveShadow = true;
+				scene.add( mesh );
 
 
 				const loader = new GLTFLoader();
 				const loader = new GLTFLoader();
-				const modelName = "models/gltf/" + model.name + ".glb";
-
-				loader.load( modelName, function ( gltf ) {
-
-					const scene = gltf.scene;
-
-					model.animations = gltf.animations;
-					model.scene = scene;
-
-					// Enable Shadows
+				loader.load( 'models/gltf/Soldier.glb', function ( gltf ) {
 
 
 					gltf.scene.traverse( function ( object ) {
 					gltf.scene.traverse( function ( object ) {
 
 
-						if ( object.isMesh ) {
-
-							object.castShadow = true;
-
-						}
+						if ( object.isMesh ) object.castShadow = true;
 
 
 					} );
 					} );
 
 
-					console.log( "Done loading model", model.name );
+					const model1 = SkeletonUtils.clone( gltf.scene );
+					const model2 = SkeletonUtils.clone( gltf.scene );
+					const model3 = SkeletonUtils.clone( gltf.scene );
 
 
-					onLoaded();
+					const mixer1 = new THREE.AnimationMixer( model1 );
+					const mixer2 = new THREE.AnimationMixer( model2 );
+					const mixer3 = new THREE.AnimationMixer( model3 );
 
 
-				} );
-
-			}
-
-			/**
-			 * Render loop. Renders the next frame of all animations
-			 */
-			function animate() {
-
-				requestAnimationFrame( animate );
+					mixer1.clipAction( gltf.animations[ 0 ] ).play(); // idle
+					mixer2.clipAction( gltf.animations[ 1 ] ).play(); // run
+					mixer3.clipAction( gltf.animations[ 3 ] ).play(); // walk
 
 
-				// Get the time elapsed since the last frame
+					model1.position.x = - 2;
+					model2.position.x = 0;
+					model3.position.x = 2;
 
 
-				const mixerUpdateDelta = clock.getDelta();
-
-				// Update all the animation frames
-
-				for ( let i = 0; i < mixers.length; ++ i ) {
-
-					mixers[ i ].update( mixerUpdateDelta );
-
-				}
-
-				renderer.render( worldScene, camera );
-
-			}
+					scene.add( model1, model2, model3 );
+					mixers.push( mixer1, mixer2, mixer3 );
 
 
-			//////////////////////////////
-			// General Three.JS stuff
-			//////////////////////////////
-			// This part is not anyhow related to the cloning of models, it's just setting up the scene.
+					animate();
 
 
-			/**
-			 * Initialize ThreeJS scene renderer
-			 */
-			function initRenderer() {
+				} );
 
 
-				const container = document.getElementById( 'container' );
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.outputEncoding = THREE.sRGBEncoding;
 				renderer.outputEncoding = THREE.sRGBEncoding;
 				renderer.shadowMap.enabled = true;
 				renderer.shadowMap.enabled = true;
-				renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-				container.appendChild( renderer.domElement );
+				document.body.appendChild( renderer.domElement );
 
 
-			}
+				window.addEventListener( 'resize', onWindowResize );
 
 
-			/**
-			 * Initialize ThreeJS THREE.Scene
-			 */
-			function initScene() {
+			}
 
 
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
-				camera.position.set( 3, 6, - 10 );
-				camera.lookAt( 0, 1, 0 );
+			function onWindowResize() {
 
 
-				clock = new THREE.Clock();
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
 
 
-				worldScene = new THREE.Scene();
-				worldScene.background = new THREE.Color( 0xa0a0a0 );
-				worldScene.fog = new THREE.Fog( 0xa0a0a0, 10, 22 );
+				renderer.setSize( window.innerWidth, window.innerHeight );
 
 
-				const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
-				hemiLight.position.set( 0, 20, 0 );
-				worldScene.add( hemiLight );
+			}
 
 
-				const dirLight = new THREE.DirectionalLight( 0xffffff );
-				dirLight.position.set( - 3, 10, - 10 );
-				dirLight.castShadow = true;
-				dirLight.shadow.camera.top = 10;
-				dirLight.shadow.camera.bottom = - 10;
-				dirLight.shadow.camera.left = - 10;
-				dirLight.shadow.camera.right = 10;
-				dirLight.shadow.camera.near = 0.1;
-				dirLight.shadow.camera.far = 40;
-				worldScene.add( dirLight );
+			function animate() {
 
 
-				// ground
-				const groundMesh = new THREE.Mesh(
-					new THREE.PlaneGeometry( 40, 40 ),
-					new THREE.MeshPhongMaterial( {
-						color: 0x999999,
-						depthWrite: false
-					} )
-				);
-
-				groundMesh.rotation.x = - Math.PI / 2;
-				groundMesh.receiveShadow = true;
-				worldScene.add( groundMesh );
-				window.addEventListener( 'resize', onWindowResize );
+				requestAnimationFrame( animate );
 
 
-			}
+				const delta = clock.getDelta();
 
 
-			/**
-			 * A callback that will be called whenever the browser window is resized.
-			 */
-			function onWindowResize() {
+				for ( const mixer of mixers ) mixer.update( delta );
 
 
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.render( scene, camera );
 
 
 			}
 			}