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

时间:2025-05-14 13:23:02
// 导入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 ); }