如何在Three.js中处理GLTF/FBX单AnimationClip网格动画及切换淡入淡出
你不需要将单流动画拆分为多个AnimationClip,Three.js的AnimationAction支持对同一Clip创建多个独立播放实例,结合时间范围限制和Action自带的交叉淡入淡出接口,即可实现需求。
实现方案
1. 初始化基础组件
首先加载FBX资源后,获取单流动画的完整Clip,提前将帧号区间转换为时间区间(单位为秒),转换规则为时间 = 帧号 / FBX导出帧率,注意帧率要和FBX导出时的配置保持一致,避免时间计算偏差。
示例代码如下:
import * as THREE from 'three' import { FBXLoader } from 'three/addons/loaders/FBXLoader.js' const fbxLoader = new FBXLoader() const clock = new THREE.Clock() let mixer, currentAction, nextAction, currentSeg const FPS = 30 // 替换为你的FBX导出帧率 // 自定义动画片段配置,按需扩展 const animationSegments = { idle: { start: 0 / FPS, end: 10 / FPS }, walk: { start: 11 / FPS, end: 22 / FPS }, run: { start: 23 / FPS, end: 35 / FPS } } // 加载FBX资源 fbxLoader.load('your-model.fbx', (fbx) => { const fullAnimationClip = fbx.animations[0] // 初始化动画混合器 mixer = new THREE.AnimationMixer(fbx) // 为同一个Clip创建两个独立的Action实例,用于交叉淡入 currentAction = mixer.clipAction(fullAnimationClip) nextAction = mixer.clipAction(fullAnimationClip) // 初始播放Idle动画 currentSeg = animationSegments.idle playSegment(currentSeg, currentAction) // 将模型加入场景 scene.add(fbx) })
2. 单片段循环播放控制
封装片段播放方法,同时在帧更新逻辑中限制Action的播放时间始终在对应片段的区间内,实现循环播放:
function playSegment(seg, action) { action.reset() action.time = seg.start action.setLoop(THREE.LoopRepeat, Infinity) action.timeScale = 1 action.play() } // 帧更新逻辑 function animate() { requestAnimationFrame(animate) const delta = clock.getDelta() if (mixer) mixer.update(delta) // 限制当前激活Action的播放区间 if (currentAction?.isRunning()) { if (currentAction.time > currentSeg.end) { currentAction.time = currentSeg.start + (currentAction.time - currentSeg.end) } } // 限制切换中目标Action的播放区间 if (nextAction?.isRunning()) { const targetSeg = animationSegments[currentTargetSeg] if (nextAction.time > targetSeg.end) { nextAction.time = targetSeg.start + (nextAction.time - targetSeg.end) } } renderer.render(scene, camera) } animate()
3. 片段交叉淡入淡出切换
调用AnimationAction自带的crossFadeTo方法实现权重过渡,切换前提前将目标Action定位到对应片段的起始时间即可:
let currentTargetSeg = '' function switchAnimation(segName, fadeDuration = 0.3) { const targetSeg = animationSegments[segName] // 相同片段无需切换 if (currentSeg === targetSeg || !targetSeg) return currentTargetSeg = segName // 初始化目标Action的播放位置 playSegment(targetSeg, nextAction) // 执行交叉淡入淡出,第三个参数设置为true自动停止原Action currentAction.crossFadeTo(nextAction, fadeDuration, true) // 过渡完成后交换两个Action的引用,重置状态 setTimeout(() => { const temp = currentAction currentAction = nextAction nextAction = temp currentSeg = targetSeg }, fadeDuration * 1000) } // 调用示例:切换到Walk动画,过渡时长0.3秒 switchAnimation('walk', 0.3)
注意事项
- 如果单流动画包含变形目标动画、属性动画等非骨骼动画内容,上述逻辑无需修改即可直接适配
- 淡入淡出时长建议不超过两个切换片段中最短时长的1/2,避免过渡过程中出现动作跳变
- 如需支持倒放、变速效果,直接修改对应Action的
timeScale属性即可,时间区间限制逻辑会自动适配 - 如果需要精确控制过渡曲线,可以手动修改两个Action的
weight属性,无需使用封装好的crossFadeTo方法
内容的提问来源于stack exchange,提问作者curiousJorgeXX
相关产品推荐
相关产品推荐

