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在处理复杂动画需求时的强大能力和灵活性。

相关推荐
NAGNIP11 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab12 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab12 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP16 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年16 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼16 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS16 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区17 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈17 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang18 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx