Three.js Animation动画系统概念及应用

3/26/2023 three.js

# 0. 写在开头

本文秉承Talk is cheap, show me the code原则,阐述动画原理,做到文字精简,一切交由代码~

在最后试着构建坤哥的唱跳rap (opens new window)的动画

# 1. Animation

动画由四个要素构成:关键帧KeyFrame、关键帧轨迹KeyframeTrack、动画剪辑AnimationClip以及动画混合器AnimationMixer

# 2. KeyFrame

关键帧由三个要素构成:timepropertyvalue,例如:在0.1spostion(1,0,0)

若关键帧中定义的属性在对象中存在,那么将更新对象的属性值,从而形成动画,例如:位置关键帧可为具有position属性的对象设置动画,缩放关键帧可为具有scale属性的对象设置动画

伪代码如下

class KeyFrame {
  constructor(p,s,r){
    this.position = p
    this.scale = s
    this.rotation = r
    // this.matrix = m 更常用的是矩阵
  }
}
class Obj {
  constructor(p){
    this.position = p
    // this.matrix = m
  }
}

function animation(keyFrame, obj){
  for(let key in keyFrame) {
    if(key in obj) obj[key] = keyFrame[key] // 只更新帧和对象共用的属性
  }  
}

// 假如在60fps下,定义1s内60个关键帧集
let keyFrames = [ kf1, kf2, ..., kf60 ]
for(let keyFrame of keyFrames) animation(keyFrames, obj) // 生成60fps动画

# 3. KeyframeTrack

关键帧轨迹定义每个时刻的帧,由三个元素构成:时刻集帧值集以及对象的key

每个单独的动作存储在每个单独的帧轨迹中。例如坤哥的唱跳rap (opens new window):手臂动作存储在手臂帧轨迹,腿部动作存储在腿部帧轨迹,还有中分发型帧轨迹、篮球帧轨迹等等(无冒犯的意思,单纯为了形象举例说明~)

在threejs中KeyframeTrack是基类,子类命名为TypeKeyframeTrack

import { TypeKeyframeTrack } from 'three' // Type对应keyFrames数组类型
let keyKF = new TypeKeyframeTrack('.key', times, values)
import { NumberKeyframeTrack } from "three" // keyFrames数组类型为Number

let times = [0, 1, 2, 3, 4] // 时刻集
let values = [0, 1, 0, 1, 0] // 帧值集
let opacityKF = new NumberKeyframeTrack('.material.opacity', times, values) // 帧轨迹
// 该轨迹定义了每个时刻对象的材料透明度
// 0s -> 0
// 1s -> 1
// 2s -> 0
// 3s -> 1
// 4s -> 0
import { VectorKeyframeTrack } from "three"

let times = [0, 3, 6]
let values = [0, 0, 0, 2, 2, 2, 0, 0, 0]
let positionKF = new VectorKeyframeTrack(".hand.position", times, values)
// 0s -> (0,0,0)
// 3s -> (2,2,2)
// 6s -> (0,0,0)

# 4. AnimationClip

动画剪辑是附加到对象的所有关键帧轨迹的集合,由三个要素构成:剪辑名称剪辑时长轨迹集

不多BB,上代码

import { AnimationClip, VectorKeyframeTrack } from "three"

let times = [0, 3, 6]
let values = [0, 0, 0, 2, 2, 2, 0, 0, 0]
let positionKF = new VectorKeyframeTrack(".position", times, values)

let tracks = [ positionKF ] // 轨迹集
let clipName = 'demo' // 剪辑名称
let clipLength = -1 // 剪辑时长,设置为-1时取轨迹集中最长轨迹时长,这里即为6s

let clip = new AnimationClip(clipName, clipLength, tracks) // 动画片段

# 5. AnimationMixer

AnimationMixer用于生成模型的动作,即动画混合器,将静态模型转化为动画混合模型mixer

通过mixer.clipAction可将AnimationClipmixer关联,以生成动作

let mixer = new AnimationMixer(model) // 创建model的混合对象
mixer.clipAction(animationClip) // 将animationClip接入到混合对象中,生成对象的动作
import { AnimationClip, NumberKeyframeTrack, VectorKeyframeTrack, AnimationMixer } from "three"
import { model } from './models/model1.js'

let positionKF = new VectorKeyframeTrack( // 三维向量轨迹
  ".position",
  [0, 3, 6],
  [0, 0, 0, 2, 2, 2, 0, 0, 0]
)

let opacityKF = new NumberKeyframeTrack( // 材料透明度轨迹
  ".material.opacity",
  [0, 1, 2, 3, 4, 5, 6],
  [0, 1, 0, 1, 0, 1, 0]
);

let moveBlinkClip = new AnimationClip("move-n-blink", -1, [ // 动画剪辑
  positionKF,
  opacityKF,
])

let mixer = new AnimationMixer(model) // 将静态的model转化为动画对象
let action = mixer.clipAction(moveBlinkClip) // 给model设置动画片段生成动作

# 6. 唱跳rap动画生成

一般来说,通过手动写代码来定义KeyFrameTrackAnimationClip十分硬核,其中涉及到大量的数学计算!所以一般都是通过外部程序在创建模型的同时定义模型动画,在通过Loader加载模型时会将AnimationClip存储在模型的animation数组中

巅峰迎来虚伪的拥护,黄昏见证真正的信徒,所以让我们通过写代码的方式,记录下哥哥每一帧的美好~

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // webGL Transform Format Loader
import { AnimationMixer } from 'three'

let _GLTFLoader = new GLTFLoader()

let cxkModelData = await _GLTFLoader.loadAsync('./assets/models/cxk.glb') // cxk模型数据,具体内容参考最底部
let cxkModel = cxkModelData.scene.children[0] // cxk模型,这个模型数据定义的scene只有一个child,所以取scene.children[0]即可
let clip = cxkModelData.animations[0] // cxk唱跳rap动画剪辑

let cxkMixer = new AnimationMixer(cxkModel) // cxk模型混合器
let sing_dance_rap_action = cxkMixer.clipAction(clip) // 将动画剪辑接入到混合器中,以生成唱跳rap动作

sing_dance_rap_action.play() // 哥哥开始表演才艺了(但是ikun只能看一眼,想多看几眼需要在渲染器的动画循环每一帧中实时更新cxkMixer)

renderer.setAnimationLoop(() => { // 设置渲染器的动画循环,在每一帧中更新cxkMixer
  cxkMixer.update()
  renderer.render(scene, camera)
})



// cxkModelData长这样
{
  animations: [AnimationClip], // 哥哥的动画剪辑clip在这个集合里
  scene: {
    type: 'Group',
    children: [Mesh], // 哥哥的模型model在这个集合里
    // ...
  },
  // 以下属性暂时还用不上
  asset: { // 模型的一些外部信息
    genetator: 'Khronos Blender glTF 2.0 I/O', // 生成模型的接口
    version: '2.0' // 版本号
  },
  cameras: [... ], 
  userData: { ... } // 用户自定义数据
  // ...
}

# 参考

1. The three.js Animation System (opens new window)

2. 蔡徐坤原版无特效打篮球视频 (opens new window)

    人生如梦,
    我投入了的却是真情,
    世界先爱了我,
    我不能不爱它。
    红莲华
    x
    loading...