# 简介

three.js 中动画也是很重要的一环。在使用软件创建模型时,一般都会创建模型的骨骼动画用于在开发中使用。下面我们加载 .fbx 格式的文件模型(它除了包含几何、材质信息,可以存储骨骼动画等数据)来实现动画。

# AnimationMixer

  • 动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
  • 参数: rootObject 混合器播放的动画所属的对象。就是包含动画模型的场景对象。
  • 常用参数和属性:
  1. .time 全局的混合器时间。
  2. .clipAction(AnimationClip) 返回所传入的剪辑参数的 AnimationAction 对象。 AnimationAction 用来调度存储在 AnimationClip 中的动画。
  • AnimationClip 动画剪辑,是一个可重用的关键帧轨道集,它代表动画。
  1. .getRoot() 返回混合器的根对象。
  2. .update() 推进混合器时间并更新动画。在渲染函数中调用更新动画。

# 开始使用

# 基础场景

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学习</title>
  </head>
  <body>
    <canvas id="c2d" class="c2d" width="1000" height="500"></canvas>
    <script type="module">
      import * as THREE from './file/three.js-dev/build/three.module.js'
      import { OrbitControls } from './file/three.js-dev/examples/jsm/controls/OrbitControls.js'
      import { FBXLoader } from './file/three.js-dev/examples/jsm/loaders/FBXLoader.js'
      const canvas = document.querySelector('#c2d')
      // 渲染器
      const renderer = new THREE.WebGLRenderer({ canvas })
      const fov = 40 // 视野范围
      const aspect = 2 // 相机默认值 画布的宽高比
      const near = 0.1 // 近平面
      const far = 10000 // 远平面
      // 透视投影相机
      const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
      camera.position.set(1000, 500, 1500)
      camera.lookAt(0, 0, 0)
      // 控制相机
      const controls = new OrbitControls(camera, canvas)
      controls.update()
      // 场景
      const scene = new THREE.Scene()
      // 背景
      scene.background = new THREE.Color(0x87ceeb)
      // 雾
      scene.fog = new THREE.Fog(0x87ceeb, 200, 10000)
      // 辅助
      const axes = new THREE.AxisHelper(700)
      scene.add(axes)
      {
        // 灯光
        const skyColor = 0xffffff // 天空 白色
        const groundColor = 0x000000 // 地面 黑色
        const intensity = 1
        const light = new THREE.HemisphereLight(skyColor, groundColor, intensity)
        scene.add(light)
      }
      {
        // 地面
        const loader = new THREE.TextureLoader()
        const texture = loader.load('./file/23/1.jpg')
        texture.wrapS = THREE.RepeatWrapping
        texture.wrapT = THREE.RepeatWrapping
        texture.magFilter = THREE.NearestFilter
        // 纹理 重复
        texture.repeat.set(100, 100)
        const planeGeo = new THREE.PlaneGeometry(10000, 10000)
        const planeMat = new THREE.MeshPhongMaterial({
          map: texture,
          side: THREE.DoubleSide
        })
        const mesh = new THREE.Mesh(planeGeo, planeMat)
        mesh.rotation.x = Math.PI * -0.5
        scene.add(mesh)
      }
      // 渲染
      function render() {
        renderer.render(scene, camera)
        requestAnimationFrame(render)
      }
      requestAnimationFrame(render)
    </script>
  </body>
</html>

# 加载.fbx 模型

const loader = new FBXLoader()
loader.load('./file/Naruto.fbx', function (mesh) {
    console.log(' AnimationMixer 动画混合器(二十四).html ~ line 73 ~ mesh', mesh)
    mesh.position.y = 110
    scene.add(mesh)
})

image.png

  • 可以看见解析出来的对象中,存在 animations 属性并且有 27 条数据,代表有 27 个动画。

image.png

# 使用 AnimationMixer 控制动画

# 1. 创建全局参数获取动画相关对象。

let actions = [] // 所有的动画数组
let gui = {} // 动画控制
let mixer = null // AnimationMixer 对象

# 2. 解析动画,并执行第 24 个动画。

mixer = new THREE.AnimationMixer(mesh)
for (var i = 0; i < mesh.animations.length; i++) {
    actions[i] = mixer.clipAction(mesh.animations[i])
}
gui['action'] = function (s) {
    for (var j = 0; j < actions.length; j++) {
        if (j === s) {
            actions[j].play()
        } else {
            actions[j].stop()
        }
    }
}
// 第 24 个动作是鸣人站立的动作
gui['action'](24)
  1. 在渲染函数中执行 .update() 函数,推进动画进行。
  • .Clock() 该对象用于跟踪时间。
const clock = new THREE.Clock()
// 渲染
function render() {
    const time = clock.getDelta()
    if (mixer) {
        mixer.update(time)
    }
    renderer.render(scene, camera)
    requestAnimationFrame(render)
}

1.gif

# 通过键盘空格切换动画

  • 监听键盘事件,调用 gui 对象执行新的动画。
let keyNum = 24 // 动作
document.onkeydown = function (e) {
    if (e && e.keyCode == 32) {
        if (keyNum === 27) {
            keyNum = 1
        }
        keyNum += 1
        gui['action'](keyNum)
    }
}

1.gif