Skip to content

创建动画

name - string,动画的名称。
property - string,将应用动画的对象的属性。 例如 position 是 Vector3 属性或 position.x 是浮点数属性。
frames per second - number,每秒动画帧数(与每秒的场景渲染帧数无关)。
property type - number,属性参数的属性类型。这可以使用以下常量进行设置:
  BABYLON.Animation.ANIMATIONTYPE_COLOR3
  BABYLON.Animation.ANIMATIONTYPE_FLOAT
  BABYLON.Animation.ANIMATIONTYPE_MATRIX
  BABYLON.Animation.ANIMATIONTYPE_QUATERNION
  BABYLON.Animation.ANIMATIONTYPE_VECTOR2
  BABYLON.Animation.ANIMATIONTYPE_VECTOR3
loop mode - number,循环模式,数字可选,可以使用以下参数设置:
  BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE - 从初始值重新启动动画
  BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT - 在最终值处暂停动画
  BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE - 使用键值渐变重复动画递增。

javascript
const myAnim = new BABYLON.Animation(name, property, frames per second, property type, loop mode)

设置关键帧

myKeys 是一个对象数组。每个对象都有两个属性:
  frame - 帧号
  value - 用于被更改的属性
一旦构建,它就会被添加到动画中:

javascript
myAnim.setKeys(myKeys)

开始动画

为了运行动画,它被推送到网格的动画数组属性上:

javascript
mesh.animations.push(myAnim)
javascript
// target - object,要动画化的 Babylon.js 对象
// from - number,开始动画的帧
// to - number,结束动画的帧
// 希望动画循环播放时,第四个参数为 true
scene.beginAnimation(target, from, to, true)

案例

javascript
  let animation
  // 创建动画,90 / 60 = 1.5s,90帧,60帧每秒,1.5秒完成动画
  const createAnimation = (thing, direction) => {
    const keys: any = []
    keys.push({
      frame: 0,
      value: thing.position.clone()
    })
    keys.push({
      frame: 45,
      value: thing.position.add(direction.scale(1)) // 沿着direction方向前进 1 单位
    })
    keys.push({
      frame: 90,
      value: thing.position // 后退 1 单位(即返回原位置)
    })

    if(!animation) { // 防止重复new
      animation = new Animation(
        'boxAnimation',
        'position',
        60, // 帧率
        Animation.ANIMATIONTYPE_VECTOR3,
        Animation.ANIMATIONLOOPMODE_CYCLE
      )
      thing.animations.push(animation)
    }

    animation.setKeys(keys)
   
    scene.beginAnimation(thing, 0, 90, true) // 设置true为循环播放
  }

动画和 Promise

从 Babylon.js v3.3 开始,可以使用 Promise 等待动画结束

javascript
let anim = scene.beginAnimation(box1, 0, 100, false)
console.log('before')
await anim.waitAsync()
console.log('after')

控制动画

每个动画都有一个名为 currentFrame 的属性,它指示了当前动画关键帧。
对于高级关键帧动画,可以定义用于在关键点之间插入(过渡)的函数。
默认情况下,这些函数如下:
  floatInterpolateFunction
  quaternionInterpolateFunction
  quaternionInterpolateFunctionWithTangents
  vector3InterpolateFunction
  vector3InterpolateFunctionWithTangents
  vector2InterpolateFunction
  vector2InterpolateFunctionWithTangents
  sizeInterpolateFunction
  color3InterpolateFunction
  matrixInterpolateFunction

javascript
BABYLON.Animation.prototype.floatInterpolateFunction = function (
  startValue,
  endValue,
  gradient
) {
  return startValue + (endValue - startValue) * gradient
}
BABYLON.Animation.prototype.quaternionInterpolateFunction = function (
  startValue,
  endValue,
  gradient
) {
  return BABYLON.Quaternion.Slerp(startValue, endValue, gradient)
}
BABYLON.Animation.prototype.vector3InterpolateFunction = function (
  startValue,
  endValue,
  gradient
) {
  return BABYLON.Vector3.Lerp(startValue, endValue, gradient)
}

辅助函数

可以使用扩展函数来创建快速动画
为了能够使用此功能,需要知道:
  动画将具有预定义的关键帧(仅生成 2 个关键帧:开始和结束)
  动画仅适用于 AbstractMesh 对象
  动画在方法调用之后立即开始

javascript
BABYLON.Animation.CreateAndStartAnimation(
  name,
  mesh,
  targetProperty,
  framePerSecond,
  totalFrame,
  from,
  to,
  loopMode,
  undefined,
  () => {}
)
javascript
BABYLON.Animation.CreateAndStartAnimation(
  'boxscale',
  box1,
  'scaling.x',
  30,
  120,
  1.0,
  1.5
)

动画权重

从 Babylon.js 3.2 开始,可以启动具有特定权重的动画。这意味着可以使用此 API 在同一目标上同时运行多个动画。最终值将是基于所有动画权重值进行加权计算后的和。
要使用权重启动动画,可以使用新的 scene.beginWeightedAnimation API:
该函数接受以下参数:
  target - any,目标
  from - number,fps 起始帧
  to - number,fps 结束帧
  weight - number,可选,此动画的权重。 默认为 1.0
  loop - boolean,可选,如果为真,动画将循环播放(取决于 BABYLON.Animation.ANIMATIONLOOPMODE)
  speedRatio - number,可选,默认值:1。此动画的速度比
  onAnimationEnd - function,可选,动画结束时触发的函数,即使手动停止动画(也依赖于 ANIMATIONLOOPMODE)
  animatable - Animatable,可选,可选的特定动画

javascript
// 权重为 1.0
let idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true)
// 权重为 0
let walkAnim = scene.beginWeightedAnimation(skeleton, 90, 124, 0, true)
// 权重为 0
let runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true)

与 beginAnimation 一样,此函数返回一个动画对象,但在这里对象的 weight 属性被设置为一个值。
还可以随时设置任何 Animatable 的 weight 属性以切换到加权模式。该值必须介于 0 和 1 之间。同样,可以将其设置为 -1 以关闭权重模式。如果将权重设置为 0,则动画将被视为暂停。

javascript
let idleAnim = scene.beginWeightedAnimation(skeleton, 0, 89, 1.0, true)
let runAnim = scene.beginWeightedAnimation(skeleton, 125, 146, 0, true)
idleAnim.weight = 0.5
runAnim.weight = 0.5

如果动画不是相同的长度(从开始帧到结束帧之间的长度相同),那么需要使用以下代码打开动画同步:

javascript
// 同步动画
idleAnim.syncWith(runAnim)

要禁用动画同步,只需调用

javascript
animation.syncWith(null)

将事件附加到动画

从 Babylon.js 版本 2.3 开始,可以将动画事件附加到动画的特定帧

javascript
// 创建事件的3个参数:
// - 触发事件的帧
// - 要执行的动作
// - 如果事件应该只执行一次,则为布尔值(默认为 false)
let event1 = new BABYLON.AnimationEvent(
  50,
  function () {
    console.log('Yeah!')
  },
  true
)
// 将的事件附加到的动画中
animation.addEvent(event1)

烘焙纹理动画

动画由 CPU 计算,将骨骼效果应用于网格。这相当慢,并且对于多个动画对象来说可能是一个瓶颈。
优化这一点的一种方法是预先计算(或烘焙)动画,将它们存储到纹理(通常称为顶点动画纹理或 VAT)中并在顶点着色器上使用它。
这释放了 CPU,但需要权衡执行此初始烘焙步骤(可以在开发时完成)、在下载中添加新纹理文件并消耗更多 GPU 内存。
这种权衡通常非常好,因为 CPU 往往是瓶颈。

javascript
let baker = null,
  mesh = null
const animationRanges = [
  {
    from: 1,
    to: 20,
    name: 'My animation'
  }
]
BABYLON.SceneLoader.ImportMeshAsync(
  '',
  'https://raw.githubusercontent.com/RaggarDK/Baby/baby/',
  'arr.babylon',
  scene,
  undefined
)
  .then((importResult) => {
    mesh = importResult.meshes[0]
    // 创建面烘焙助手,这样就可以生成纹理
    baker = new BABYLON.VertexAnimationBaker(scene, mesh)
    // 可以在此处使用多个动画范围对动画进行切片
    return baker.bakeVertexData(ranges)
  })
  .then((vertexData) => {
    // 得到了顶点数据。 从中创建纹理:
    const vertexTexture = baker.textureFromBakedVertexData(vertexData)
    // 创建一个管理器来存储它。
    const manager = new BABYLON.BakedVertexAnimationManager(scene)
    // 存储纹理
    manager.texture = vertexTexture
    // 设置动画参数。 可以随时更改此设置。
    manager.setAnimationParameters(
      animationRanges[0].from, // 初始帧
      animationRanges[0].to, // 最后一帧
      0, // offset
      30 // 每秒帧数
    )
    // 将管理器与网格相关联
    mesh.bakedVertexAnimationManager = manager
    // 更新播放动画的时间
    scene.registerBeforeRender(() => {
      manager.time += engine.getDeltaTime() / 1000.0
    })
  })

VAT 的实例

实例是使用硬件加速渲染绘制大量相同网格的绝佳方式。
VAT 可以进一步用于有效地处理动画。
在这种情况下,需要注册一个缓冲区来为每个实例设置动画参数:

javascript
// 创建实例化缓冲区
mesh.registerInstancedBuffer('bakedVertexAnimationSettingsInstanced', 4)
// 为基础网格设置它
mesh.instancedBuffers.bakedVertexAnimationSettingsInstanced =
  new BABYLON.Vector4(0, 0, 0, 0)

精简实例的 VAT

VAT 也可用于精简实例。然后为精简实例设置参数:

javascript
// 分配参数
const animParameters = new Float32Array(numInstances * 4)
// 对于每个实例
for (let i = 0; i < numInstances; i++) {
  // 使用的代码生成动画参数:
  // returns a BABYLON.Vector4()
  const params = setAnimationParameters()
  // 存储在基本数组中
  animParameters.set(params.asArray(), i * 4)
}
// 使用所有设置更新网格
mesh.thinInstanceSetBuffer(
  'bakedVertexAnimationSettingsInstanced',
  animParameters,
  4
)