03.three官方示例+编辑器+AI快速学习webgl_animation_multiple
// 导入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 );
}