03.three官方示例+编辑器+AI快速学习webgl_animation_multiple

本实例主要讲解内容

这个示例展示了Three.js中骨骼动画的高级应用技巧,重点演示了如何使用SkeletonUtils.clone()方法复制模型,并展示了两种不同的骨骼动画管理方式:

  1. 独立骨骼模式:每个模型拥有独立的骨骼结构,可播放不同动画
  2. 共享骨骼模式:多个模型共享同一骨骼结构,同步播放相同动画

通过对比这两种模式,展示了它们各自的特点和适用场景,以及如何在Three.js中实现这些技术。

完整代码注释

html 复制代码
<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Multiple animated skinned meshes</title>
		<meta charset="utf-8">
		<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">
	</head>
	<body>
		<!-- 信息面板,显示示例说明和模型来源 -->
		<div id="info">
			This demo shows the usage of <strong>SkeletonUtils.clone()</strong> and how to setup a shared skeleton.<br/>
			Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
		</div>

		<!-- 导入映射,指定模块导入路径 -->
		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.module.js",
					"three/addons/": "./jsm/"
				}
			}
		</script>

		<script type="module">

			// 导入Three.js核心库和辅助工具
			import * as THREE from 'three';

			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';  // GLTF模型加载器
			import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';  // 骨骼工具集
			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';  // GUI控制面板

			// 全局变量定义
			let camera, scene, renderer, clock;  // 相机、场景、渲染器和时钟
			let model, animations;  // 原始模型和动画

			// 存储动画混合器和场景对象的数组
			const mixers = [], objects = [];

			// 控制面板参数
			const params = {
				sharedSkeleton: false  // 是否使用共享骨骼模式
			};

			// 初始化函数
			init();

			function init() {

				// 创建透视相机,设置位置和朝向
				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.set( 2, 3, - 6 );
				camera.lookAt( 0, 1, 0 );

				// 创建时钟,用于计算动画时间增量
				clock = new THREE.Clock();

				// 创建场景并设置背景和雾
				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xa0a0a0 );
				scene.fog = new THREE.Fog( 0xa0a0a0, 10, 50 );

				// 添加半球光,提供自然光照效果
				const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x8d8d8d, 3 );
				hemiLight.position.set( 0, 20, 0 );
				scene.add( hemiLight );

				// 添加方向光,用于产生阴影
				const dirLight = new THREE.DirectionalLight( 0xffffff, 3 );
				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 );

				// 可选:显示阴影相机辅助线,用于调试阴影
				// scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );

				// 创建地面平面
				const mesh = new THREE.Mesh( new THREE.PlaneGeometry( 200, 200 ), new THREE.MeshPhongMaterial( { color: 0xcbcbcb, depthWrite: false } ) );
				mesh.rotation.x = - Math.PI / 2;  // 旋转平面使其水平
				mesh.receiveShadow = true;  // 地面接收阴影
				scene.add( mesh );

				// 加载GLTF格式模型
				const loader = new GLTFLoader();
				loader.load( 'models/gltf/Soldier.glb', function ( gltf ) {

					model = gltf.scene;  // 获取模型场景对象
					animations = gltf.animations;  // 获取模型动画

					// 遍历模型的所有对象,设置可投射阴影
					model.traverse( function ( object ) {
						if ( object.isMesh ) object.castShadow = true;
					} );

					// 设置默认场景(独立骨骼模式)
					setupDefaultScene();

				} );

				// 初始化WebGL渲染器
				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );  // 设置像素比,适配高DPI屏幕
				renderer.setSize( window.innerWidth, window.innerHeight );  // 设置渲染尺寸
				renderer.setAnimationLoop( animate );  // 设置动画循环
				renderer.shadowMap.enabled = true;  // 启用阴影渲染
				document.body.appendChild( renderer.domElement );  // 将渲染器DOM元素添加到页面

				// 添加窗口大小变化事件监听,调整相机和渲染器
				window.addEventListener( 'resize', onWindowResize );

				// 创建GUI控制面板
				const gui = new GUI();

				// 添加共享骨骼模式切换按钮,并绑定事件处理函数
				gui.add( params, 'sharedSkeleton' ).onChange( function () {
					clearScene();  // 清空当前场景
			
					if ( params.sharedSkeleton === true ) {
						setupSharedSkeletonScene();  // 设置共享骨骼场景
					} else {
						setupDefaultScene();  // 设置默认场景(独立骨骼)
					}
			
				} );
				gui.open();  // 默认打开控制面板

			}

			// 清空场景的函数
			function clearScene() {

				// 停止所有动画混合器的动作
				for ( const mixer of mixers ) {
					mixer.stopAllAction();
				}

				// 清空混合器数组
				mixers.length = 0;

				// 从场景中移除所有对象,并释放骨骼资源
				for ( const object of objects ) {
					scene.remove( object );

					scene.traverse( function ( child ) {
						if ( child.isSkinnedMesh ) child.skeleton.dispose();  // 释放骨骼资源
					} );
				}

				// 清空对象数组
				objects.length = 0;
			}

			// 设置默认场景(独立骨骼模式)
			function setupDefaultScene() {

				// 三个克隆模型,每个拥有独立的骨骼结构
				// 每个模型可以有自己独立的动画状态

				const model1 = SkeletonUtils.clone( model );  // 克隆第一个模型
				const model2 = SkeletonUtils.clone( model );  // 克隆第二个模型
				const model3 = SkeletonUtils.clone( model );  // 克隆第三个模型

				// 设置模型位置
				model1.position.x = - 2;
				model2.position.x = 0;
				model3.position.x = 2;

				// 为每个模型创建独立的动画混合器
				const mixer1 = new THREE.AnimationMixer( model1 );
				const mixer2 = new THREE.AnimationMixer( model2 );
				const mixer3 = new THREE.AnimationMixer( model3 );

				// 为每个模型播放不同的动画
				mixer1.clipAction( animations[ 0 ] ).play(); // idle - 站立动画
				mixer2.clipAction( animations[ 1 ] ).play(); // run - 跑步动画
				mixer3.clipAction( animations[ 3 ] ).play(); // walk - 行走动画

				// 将模型添加到场景
				scene.add( model1, model2, model3 );
			
				// 将模型和混合器添加到管理数组
				objects.push( model1, model2, model3 );
				mixers.push( mixer1, mixer2, mixer3 );

			}

			// 设置共享骨骼场景
			function setupSharedSkeletonScene() {

				// 三个克隆模型,共享同一个骨骼结构
				// 所有模型共享相同的动画状态

				// 克隆原始模型作为共享模型基础
				const sharedModel = SkeletonUtils.clone( model );
				// 获取共享的蒙皮网格对象(假设名为'vanguard_Mesh')
				const shareSkinnedMesh = sharedModel.getObjectByName( 'vanguard_Mesh' );
				// 获取共享骨骼
				const sharedSkeleton = shareSkinnedMesh.skeleton;
				// 获取共享的根骨骼(假设名为'mixamorigHips')
				const sharedParentBone = sharedModel.getObjectByName( 'mixamorigHips' );
				// 将根骨骼添加到场景,骨骼需要在场景中才能使动画生效
				scene.add( sharedParentBone );

				// 克隆蒙皮网格创建三个实例
				const model1 = shareSkinnedMesh.clone();
				const model2 = shareSkinnedMesh.clone();
				const model3 = shareSkinnedMesh.clone();

				// 设置绑定模式为分离模式,允许使用外部骨骼
				model1.bindMode = THREE.DetachedBindMode;
				model2.bindMode = THREE.DetachedBindMode;
				model3.bindMode = THREE.DetachedBindMode;

				// 创建单位矩阵
				const identity = new THREE.Matrix4();

				// 使用共享骨骼绑定每个模型实例
				model1.bind( sharedSkeleton, identity );
				model2.bind( sharedSkeleton, identity );
				model3.bind( sharedSkeleton, identity );

				// 设置模型位置
				model1.position.x = - 2;
				model2.position.x = 0;
				model3.position.x = 2;

				// 应用从GLTF资产中获取的变换
				model1.scale.setScalar( 0.01 );
				model1.rotation.x = - Math.PI * 0.5;
				model2.scale.setScalar( 0.01 );
				model2.rotation.x = - Math.PI * 0.5;
				model3.scale.setScalar( 0.01 );
				model3.rotation.x = - Math.PI * 0.5;

				// 创建单个动画混合器,控制共享骨骼
				const mixer = new THREE.AnimationMixer( sharedParentBone );
				mixer.clipAction( animations[ 1 ] ).play(); // 播放跑步动画

				// 将共享骨骼和模型实例添加到场景
				scene.add( sharedParentBone, model1, model2, model3 );
			
				// 将对象和混合器添加到管理数组
				objects.push( sharedParentBone, model1, model2, model3 );
				mixers.push( mixer );

			}

			// 窗口大小变化事件处理函数
			function onWindowResize() {
				camera.aspect = window.innerWidth / window.innerHeight;  // 更新相机宽高比
				camera.updateProjectionMatrix();  // 更新相机投影矩阵
				renderer.setSize( window.innerWidth, window.innerHeight );  // 更新渲染器尺寸
			}

			// 动画循环函数,每一帧都会被调用
			function animate() {
				const delta = clock.getDelta();  // 获取自上一帧以来的时间增量

				// 更新所有动画混合器
				for ( const mixer of mixers ) mixer.update( delta );

				// 渲染场景
				renderer.render( scene, camera );
			}

		</script>

	</body>

</html>

整体总结

这个Three.js示例展示了骨骼动画的高级应用技巧,主要内容包括:

  1. 两种骨骼管理模式

    • 独立骨骼模式:每个模型拥有独立的骨骼结构和动画状态,可播放不同动画,占用更多资源,但灵活性高
    • 共享骨骼模式:多个模型共享同一骨骼结构,必须同步播放相同动画,节省资源,但动画一致性要求高
  2. 核心技术实现

    • 使用SkeletonUtils.clone()方法复制模型及其层次结构
    • 独立模式直接克隆整个模型并为每个模型创建独立混合器
    • 共享模式提取共享骨骼,设置DetachedBindMode,并使用单个混合器控制所有模型
  3. 性能与应用场景

    • 独立模式适合需要不同动画状态的角色(如游戏中不同行为的NPC)
    • 共享模式适合需要大量相同动画的场景(如群众演员、军队等),可显著降低GPU和CPU负担

这个示例对于需要高效管理大量骨骼动画的应用场景非常有价值,展示了Three.js在处理复杂动画需求时的强大能力和灵活性。

相关推荐
不懂嵌入式几秒前
基于深度学习的水果识别系统设计
人工智能·深度学习
江小皮不皮10 分钟前
为何选择MCP?自建流程与Anthropic MCP的对比分析
人工智能·llm·nlp·aigc·sse·mcp·fastmcp
小虎卫远程打卡app15 分钟前
视频编解码学习十一之视频原始数据
学习·视频编解码
GIS数据转换器24 分钟前
当三维地理信息遇上气象预警:电网安全如何实现“先知先觉”?
人工智能·科技·安全·gis·智慧城市·交互
网易易盾24 分钟前
AIGC时代的内容安全:AI检测技术如何应对新型风险挑战?
人工智能·安全·aigc
工头阿乐28 分钟前
PyTorch中的nn.Embedding应用详解
人工智能·pytorch·embedding
alpszero31 分钟前
YOLO11解决方案之物体模糊探索
人工智能·python·opencv·计算机视觉·yolo11
vlln38 分钟前
适应性神经树:当深度学习遇上决策树的“生长法则”
人工智能·深度学习·算法·决策树·机器学习
奋斗者1号1 小时前
机器学习之决策树与决策森林:机器学习中的强大工具
人工智能·决策树·机器学习
多巴胺与内啡肽.1 小时前
OpenCV进阶操作:风格迁移以及DNN模块解析
人工智能·opencv·dnn