更新于 

导入模型

源项目仓库:

动画模型的播放

狗狗模型是项目原作者WaelYasmina做的(kawaii捏)

模型导出成glb格式之后,一般首先在threejs提供的官方编辑器中导入,
看看模型、动画能不能正常执行

三个要素
  • THREE.AnimationMixer
    • 是Three.js中用于管理和播放模型动画额度组件
  • THREE.AnimationClip
    • 模型中包含的动画片段
  • THREE.Clock
    • 时间指挥棒

下面的步骤是为了创建好mixer,
类似于合成一条视频轨道:

1
2
3
4
5
6
7
8
9
10
11
12
13
let mixer
gltfLoader.load(doggo.href,function(gltf){
const model = gltf.scene
scene.add(model)
mixer = new THREE.AnimationMixer(model)
const clips = gltf.animations // 传入所有动画
clips.forEach(clip=>{
const action = mixer.clipAction(clip)
action.play()
})
},undefined,function(err){
console.error(err)
})

仅仅是合成好视频轨道之后,
还需要创建一个时间指示器,用于告诉mixer动画执行到哪里,
并且需要早animate函数中更新mixer:

1
2
3
4
5
6
const clock = new THREE.Clock()
function animate(){
if(mixer)
mixer.update(clock.getDelta())
renderer.render(scene , camera)
}

导入模型加入阴影

为导入的模型整体加上castShadow属性无效,
因为导入模型是由各个部位组成的,
需要遍历模型整体下的各个部分,
分别开启castShadow。

遍历子部位使用的是ThreeJs中Group类的traverse方法,
traverse的参数是一个回调函数,
作用和用法与forEach相类似

1
2
3
4
5
6
7
8
9
const gltfLoader = new GLTFLoader()
gltfLoader.load('/assets/Wolf.gltf', gltf => {
// ...
gltf.scene.traverse(node => {
if (node.isObject3D) {
node.castShadow = true
}
})
})

动画拼接

如果同时开启多个clipAction,
多个动画之间的关系不会是连续串行,
而是并行,
想要实现串行动画,
首先需要关闭动画的无限循环选项(动画默认是loop),
这样就可以触发mixer的finished事件。
finish事件会在动画全部结束后触发,
只要在finishing事件中,reset初始化并paly播放另一个动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let mixer
const gltfLoader = new GLTFLoader()
gltfLoader.load('/assets/Alpaca.gltf', gltf => {
scene.add(gltf.scene)
mixer = new THREE.AnimationMixer(gltf.scene)
const action_eat = mixer.clipAction(THREE.AnimationClip.findByName(gltf.animations, 'Eating'))
action_eat.loop = THREE.LoopOnce
action_eat.play()
const action_walk = mixer.clipAction(THREE.AnimationClip.findByName(gltf.animations, 'Idle'))
action_walk.loop = THREE.LoopOnce
// action_walk.play()

mixer.addEventListener('finished', function (e) {
if (e.action._clip.name === 'Eating') {
action_walk.reset()
action_walk.play()
} else if (e.action._clip.name === 'Walk') {
action_eat.reset()
action_eat.play()
}
})
})