19. 模型动画 1. 概述 模型动画是通过 AnimationMixer 控制模型骨骼动画或变形动画的系统。它支持播放、混合、淡入淡出等高级动画控制功能。
┌─────────────────────────────────────────────────────────────┐ │ 模型动画体系 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 核心类 │ │ ├── AnimationMixer:动画混合器 │ │ ├── AnimationClip:动画剪辑 │ │ ├── AnimationAction:动画动作 │ │ └── AnimationObjectGroup:动画对象组 │ │ │ │ 动画控制 │ │ ├── play():播放动画 │ │ ├── pause():暂停动画 │ │ ├── stop():停止动画 │ │ ├── reset():重置动画 │ │ ├── fadeIn():淡入 │ │ ├── fadeOut():淡出 │ │ └── crossFadeTo():交叉淡变 │ │ │ └─────────────────────────────────────────────────────────────┘2. AnimationMixer 2.1 基本用法 import { GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js' ; const mixer= new THREE. AnimationMixer ( model) ; // 获取动画剪辑 const clip= model. animations[ 0 ] ; // 创建动画动作 const action= mixer. clipAction ( clip) ; // 播放动画 action. play ( ) ; // 更新动画(在动画循环中) function animate ( deltaTime ) { mixer. update ( deltaTime) ; } 2.2 属性详解 const mixer= new THREE. AnimationMixer ( model) ; // 时间缩放(速度) mixer. timeScale= 1.0 ; // 当前时间 mixer. time= 0 ; // 是否正在更新 mixer. enabled= true ; // 获取所有动作 const actions= mixer. _actions; 2.3 方法详解 // 剪辑动作 const action= mixer. clipAction ( clip) ; // 停止所有动作 mixer. stopAllAction ( ) ; // 获取动作 const existingAction= mixer. existingAction ( clip) ; // 获取时间缩放 const timeScale= mixer. getTimeScale ( ) ; // 设置时间缩放 mixer. setTimeScale ( 1.5 ) ; // 更新动画 mixer. update ( deltaTime) ; 3. AnimationClip 3.1 属性详解 const clip= model. animations[ 0 ] ; // 动画名称 clip. name= 'walk' ; // 持续时间(秒) clip. duration= 2.0 ; // 帧率 clip. fps= 30 ; // 轨道数组 clip. tracks= [ ] ; 3.2 创建动画剪辑 import { AnimationClip, VectorKeyframeTrack, NumberKeyframeTrack} from 'three' ; // 位置动画 const positionTrack= new VectorKeyframeTrack ( '.position' , [ 0 , 1 , 2 ] , [ 0 , 0 , 0 , 2 , 0 , 0 , 0 , 0 , 0 ] ) ; // 旋转动画 const rotationTrack= new VectorKeyframeTrack ( '.rotation' , [ 0 , 1 , 2 ] , [ 0 , 0 , 0 , 0 , Math. PI , 0 , 0 , 0 , 0 ] ) ; // 创建剪辑 const clip= new AnimationClip ( 'myAnimation' , 2 , [ positionTrack, rotationTrack] ) ; 4. AnimationAction 4.1 属性详解 const action= mixer. clipAction ( clip) ; // 是否正在播放 action. isRunning= true ; // 是否暂停 action. paused= false ; // 是否启用 action. enabled= true ; // 时间缩放 action. timeScale= 1.0 ; // 重复次数(Infinity 无限循环) action. repetitions= Infinity ; // 循环模式 // THREE.LoopRepeat - 重复播放 // THREE.LoopOnce - 播放一次 // THREE.LoopPingPong - 来回播放 action. loop= THREE . LoopRepeat; // 权重(混合强度) action. weight= 1.0 ; // 淡入时间 action. fadeInTime= 0.5 ; // 淡出时间 action. fadeOutTime= 0.5 ; 4.2 方法详解 // 播放 action. play ( ) ; // 暂停 action. pause ( ) ; // 停止 action. stop ( ) ; // 重置 action. reset ( ) ; // 淡入 action. fadeIn ( duration) ; // 淡出 action. fadeOut ( duration) ; // 交叉淡变到另一个动作 const walkAction= mixer. clipAction ( walkClip) ; const runAction= mixer. clipAction ( runClip) ; walkAction. crossFadeTo ( runAction, 0.5 ) ; // 设置循环 action. setLoop ( THREE . LoopRepeat, Infinity ) ; // 设置持续时间 action. setDuration ( 2 ) ; // 从指定时间开始 action. startAt ( 1 ) ; 5. 动画混合 5.1 权重混合 const idleAction= mixer. clipAction ( idleClip) ; const walkAction= mixer. clipAction ( walkClip) ; idleAction. play ( ) ; walkAction. play ( ) ; // 设置权重 idleAction. weight= 0.7 ; walkAction. weight= 0.3 ; // 启用权重混合 mixer. addEventListener ( 'loop' , ( e ) => { // 循环事件 } ) ; 5.2 淡入淡出 // 从当前动作切换到另一个 function switchToWalk ( ) { walkAction. reset ( ) . fadeIn ( 0.5 ) . play ( ) ; idleAction. fadeOut ( 0.5 ) ; } function switchToRun ( ) { runAction. reset ( ) . fadeIn ( 0.3 ) . play ( ) ; walkAction. fadeOut ( 0.3 ) ; } 6. 完整示例 import * as THREE from 'three' ; import { OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js' ; import { GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js' ; const scene= new THREE. Scene ( ) ; scene. background= new THREE. Color ( 0x111122 ) ; const camera= new THREE. PerspectiveCamera ( 45 , window. innerWidth/ window. innerHeight, 0.1 , 1000 ) ; camera. position. set ( 5 , 4 , 8 ) ; camera. lookAt ( 0 , 0 , 0 ) ; const renderer= new THREE. WebGLRenderer ( { antialias : true } ) ; renderer. setSize ( window. innerWidth, window. innerHeight) ; renderer. shadowMap. enabled= true ; document. body. appendChild ( renderer. domElement) ; const controls= new OrbitControls ( camera, renderer. domElement) ; controls. enableDamping= true ; // 光源 const ambientLight= new THREE. AmbientLight ( 0x404040 , 0.5 ) ; scene. add ( ambientLight) ; const directionalLight= new THREE. DirectionalLight ( 0xffffff , 1 ) ; directionalLight. position. set ( 5 , 10 , 7 ) ; directionalLight. castShadow= true ; scene. add ( directionalLight) ; // 辅助对象 const axesHelper= new THREE. AxesHelper ( 5 ) ; scene. add ( axesHelper) ; const gridHelper= new THREE. GridHelper ( 10 , 20 ) ; scene. add ( gridHelper) ; // 创建带动画的简单模型 const group= new THREE. Group ( ) ; // 身体 const bodyGeo= new THREE. BoxGeometry ( 1 , 1.2 , 0.8 ) ; const bodyMat= new THREE. MeshStandardMaterial ( { color : 0x44aa88 , metalness : 0.5 , roughness : 0.3 } ) ; const body= new THREE. Mesh ( bodyGeo, bodyMat) ; body. castShadow= true ; group. add ( body) ; // 头部 const headGeo= new THREE. SphereGeometry ( 0.6 , 32 , 32 ) ; const headMat= new THREE. MeshStandardMaterial ( { color : 0x88aaff , metalness : 0.2 , roughness : 0.4 } ) ; const head= new THREE. Mesh ( headGeo, headMat) ; head. position. y= 0.9 ; head. castShadow= true ; group. add ( head) ; // 手臂 const armGeo= new THREE. BoxGeometry ( 0.4 , 1 , 0.4 ) ; const armMat= new THREE. MeshStandardMaterial ( { color : 0x44aa88 } ) ; const leftArm= new THREE. Mesh ( armGeo, armMat) ; leftArm. position. set ( - 0.7 , 0.5 , 0 ) ; const rightArm= new THREE. Mesh ( armGeo, armMat) ; rightArm. position. set ( 0.7 , 0.5 , 0 ) ; group. add ( leftArm, rightArm) ; scene. add ( group) ; // 创建动画剪辑 const mixer= new THREE. AnimationMixer ( group) ; // 手臂摆动动画 const armPositions= [ - 0.5 , 0.5 , 0 , 0.5 , 0.5 , 0 , - 0.5 , 0.5 , 0 ] ; const leftArmTrack= new THREE. VectorKeyframeTrack ( 'leftArm.position' , [ 0 , 0.5 , 1 ] , [ - 0.7 , 0.5 , 0 , - 0.3 , 0.5 , 0 , - 0.7 , 0.5 , 0 ] ) ; const rightArmTrack= new THREE. VectorKeyframeTrack ( 'rightArm.position' , [ 0 , 0.5 , 1 ] , [ 0.7 , 0.5 , 0 , 0.3 , 0.5 , 0 , 0.7 , 0.5 , 0 ] ) ; const armClip= new THREE. AnimationClip ( 'armSwing' , 1 , [ leftArmTrack, rightArmTrack] ) ; const armAction= mixer. clipAction ( armClip) ; armAction. play ( ) ; // 上下浮动动画 const bodyTrack= new THREE. VectorKeyframeTrack ( '.position' , [ 0 , 0.5 , 1 ] , [ 0 , 0 , 0 , 0 , 0.2 , 0 , 0 , 0 , 0 ] ) ; const floatClip= new THREE. AnimationClip ( 'float' , 1 , [ bodyTrack] ) ; const floatAction= mixer. clipAction ( floatClip) ; floatAction. play ( ) ; // 头部旋转动画 const headTrack= new THREE. VectorKeyframeTrack ( 'head.rotation' , [ 0 , 0.5 , 1 ] , [ 0 , 0 , 0 , 0 , 0.5 , 0 , 0 , 0 , 0 ] ) ; const headClip= new THREE. AnimationClip ( 'headRotate' , 1 , [ headTrack] ) ; const headAction= mixer. clipAction ( headClip) ; headAction. play ( ) ; // GUI 控制 import GUI from 'lil-gui' ; const gui= new GUI ( ) ; const animationFolder= gui. addFolder ( '动画控制' ) ; animationFolder. add ( mixer, 'timeScale' , 0 , 2 ) . name ( '播放速度' ) ; animationFolder. add ( armAction, 'timeScale' , 0 , 2 ) . name ( '手臂速度' ) ; animationFolder. add ( floatAction, 'timeScale' , 0 , 2 ) . name ( '浮动速度' ) ; animationFolder. add ( armAction, 'weight' , 0 , 1 ) . name ( '手臂权重' ) ; animationFolder. add ( floatAction, 'weight' , 0 , 1 ) . name ( '浮动权重' ) ; animationFolder. add ( { pause : ( ) => armAction. paused= ! armAction. paused} , 'pause' ) . name ( '暂停/继续' ) ; animationFolder. open ( ) ; // 地面 const planeGeometry= new THREE. PlaneGeometry ( 8 , 8 ) ; const planeMaterial= new THREE. MeshStandardMaterial ( { color : 0x336699 , side : THREE . DoubleSide} ) ; const plane= new THREE. Mesh ( planeGeometry, planeMaterial) ; plane. rotation. x= - Math. PI / 2 ; plane. position. y= - 1 ; plane. receiveShadow= true ; scene. add ( plane) ; let lastTime= 0 ; function animate ( currentTime ) { requestAnimationFrame ( animate) ; const delta= Math. min ( 0.033 , ( currentTime- lastTime) / 1000 ) ; lastTime= currentTime; mixer. update ( delta) ; controls. update ( ) ; renderer. render ( scene, camera) ; } animate ( 0 ) ; window. addEventListener ( 'resize' , onWindowResize, false ) ; function onWindowResize ( ) { camera. aspect= window. innerWidth/ window. innerHeight; camera. updateProjectionMatrix ( ) ; renderer. setSize ( window. innerWidth, window. innerHeight) ; } 7. 总结 类 用途 AnimationMixer 动画混合器 AnimationClip 动画剪辑 AnimationAction 动画动作 VectorKeyframeTrack 向量关键帧轨道 NumberKeyframeTrack 数字关键帧轨道
动画控制方法 说明 play()播放动画 pause()暂停动画 stop()停止动画 reset()重置动画 fadeIn()淡入 fadeOut()淡出 crossFadeTo()交叉淡变