Threejs入门之二十四:Threejs中的Animation动画

Threejs为我们提供了强大的动画系统接口API,通过这些接口,我们可以很轻松的实现物体的移动、旋转、缩放、颜色变化、透明度变化等各种效果,今天我们就来了解下Threejs中的动画系统。
首先我们先了解几个在Threejs动画系统中比较重要的组件

KeyframeTrack 关键帧轨道

关键帧轨道(KeyframeTrack)是关键帧(keyframes)的定时序列, 它由时间和相关值的列表组成, 用来让一个对象的某个特定属性动起来。

KeyframeTrack中总是存在两个数组:times数组按顺序存储该轨道的所有关键帧的时间值,而values数组包含动画属性的相应更改值。

值数组中的每一个成员,属于某一特定时间点,不仅可以是一个简单的数字,还可以是一个向量(如果是位置动画)或者是一个四元数(如果是旋转动画)。 因此,值数组(也是一个平面阵列)的长度可能是时间数组的三四倍。

构造函数

KeyframeTrack( name : String, times : Array, values : Array, interpolation : Constant )
name - 关键帧轨道(KeyframeTrack)的标识符
times - 关键帧的时间数组, 被内部转化为 Float32Array
values - 与时间数组中的时间点相关的值组成的数组, 被内部转化为 Float32Array
interpolation - 使用的插值类型

KeyframeTrack具体的属性和方法查看官方文档,这里不再赘述。

常用的KeyframeTrack子类

VectorKeyframeTrack:向量类型的关键帧轨道
ColorKeyframeTrack:反应颜色变化的关键帧轨道
BooleanKeyframeTrack:布尔类型的关键帧轨道
NumberKeyframeTrack:数字类型的关键帧轨道
QuaternionKeyframeTrack:四元数类型的关键帧轨道
StringKeyframeTrack:字符串类型的关键帧轨道

AnimationClip 动画剪辑

动画剪辑(AnimationClip)是一个可重用的关键帧轨道集,它用来定义动画

构造函数

AnimationClip( name : String, duration : Number, tracks : Array )
name - 此剪辑的名称
duration - 持续时间 (单位秒). 如果传入负数, 持续时间将会从传入的数组中计算得到。
tracks - 一个由关键帧轨道(KeyframeTracks)组成的数组。AnimationClip里面,每个动画属性的数据都存储在一个单独的KeyframeTrack中

Animation Mixer 动画混合器

动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。

构造函数

AnimationMixer( rootObject : Object3D )
rootObject - 混合器播放的动画所属的对象

属性

.time : Number类型;全局的混合器时间(单位秒; 混合器创建的时刻记作0时刻)
.timeScale : Number类型;全局时间(mixer time)的比例因子
注意: 将混合器的时间比例设为0, 稍后再设置为1,可以暂停/取消暂停由该混合器控制的所有动作。

常用方法

.clipAction (clip : AnimationClip, optionalRoot : Object3D) : AnimationAction
返回所传入的剪辑参数的AnimationAction, 根对象参数可选,默认值为混合器的默认根对象。第一个参数可以是动画剪辑(AnimationClip)对象或者动画剪辑的名称。

如果不存在符合传入的剪辑和根对象这两个参数的动作, 该方法将会创建一个。传入相同的参数多次调用将会返回同一个剪辑实例。
.update (deltaTimeInSeconds : Number) : 推进混合器时间并更新动画

通常在渲染循环中完成, 传入按照混合器的时间比例(timeScale)缩放过的clock.getDelta

AnimationAction 动画动作

AnimationActions 用来调控制存储在AnimationClips中的动画。通过配置AnimationAction,我们可以决定何时播放、暂停或停止其中一个混合器中的某个AnimationClip, 这个AnimationClip是否需要重复播放以及重复的频率, 是否需要使用淡入淡出或时间缩放,以及一些其他内容(例如交叉渐变和同步)。

构造函数

AnimationAction( mixer : AnimationMixer, clip : AnimationClip, localRoot : Object3D )
mixer - 被此动作控制的 动画混合器
clip - 动画剪辑 保存了此动作当中的动画数据
localRoot - 动作执行的根对象

注意: 通常我们不直接调用这个构造函数,而是先用AnimationMixer.clipAction实例化一个AnimationAction,因为这个方法提供了缓存以提高性能。

动画实例

通过上面的介绍我们了解了Threejs中动画系统的几个常用组件,下面我们通过创建一个移立方体,并使其通过threejs的动画系统移动、旋转、缩放、变色等操作来使其运动起来;
和前面章节一样,先搭建环境,代码如下,具体细节就不讲了,有备注,不了解的可以看下前面的文章

引入threejs,创建场景、相机、渲染器等

index.html中

<body> 
  <script type="importmap">
    {
    
    
      "imports":{
    
    
        "three":"../../three.js/build/three.module.js",
        "three/addons/": "../../three.js/examples/jsm/"
      }
    }
  </script>
  <script type="module" src="./index.js"></script>
</body>

index.js中代码

import * as THREE from 'three'
import {
    
     OrbitControls } from 'three/addons/controls/OrbitControls.js'


// 定义变量
let camera,scene,renderer
let axesHelper
let hesLight,dirLight
let box
let controls

// 初始化场景
initScene()
// 初始化相机
initCamera()
// 初始化辅助轴
initAxesHelper()
// 初始化灯光
initLight()
// 初始化渲染器
initRenderer()
// 循环执行
animate()

// 初始化轨道控制器
initControl()
// 窗体重置
window.addEventListener('resize', function () {
    
    
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
})
function initScene() {
    
    
  scene = new THREE.Scene()
  scene.background = new THREE.Color(0x888888)
}

function initCamera() {
    
    
  camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,100)
  camera.position.set(5,5,5)
}

function initAxesHelper() {
    
    
  axesHelper = new THREE.AxesHelper(3)
  scene.add(axesHelper)
}

function initLight() {
    
    
  hesLight = new THREE.HemisphereLight()
  hesLight.intensity = 0.3
  scene.add(hesLight)

  dirLight = new THREE.DirectionalLight()
  dirLight.position.set(5,5,-5)
  scene.add(dirLight)
}

function initControl() {
    
     
  controls = new OrbitControls(camera, renderer.domElement)
}

function initRenderer() {
    
     
  renderer = new THREE.WebGLRenderer({
    
     antialias: true }) 
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setSize(window.innerWidth,window.innerHeight)
  document.body.appendChild(renderer.domElement)
}

function animate() {
    
    
  requestAnimationFrame(animate)
  renderer.render(scene,camera)
}

创建立方体

// 初始化物体
initMeshes()
function initMeshes() {
    
    
  box = new THREE.Mesh(
    new THREE.BoxGeometry(1,1,1),
    new THREE.MeshLambertMaterial({
    
    color:0x00ff00})
  )
  scene.add(box)
}

创建动画

先创建一个initAnimation()函数并调用该函数,将动画相关的内容写入该代码块

// 创建动画
initAnimation()
function initAnimation() {
    
    
  
}

创建移动动画

首先我们来创建移动动画,我们先来定义动画的关键帧,移动动画的关键帧我们用VectorKeyframeTrack创建,在initAnimation()中添加如下代码
创建moveKeyFrame 关键帧

// 移动
  const moveKeyFrame = new THREE.VectorKeyframeTrack(
    '.position',//要控制关键帧的名称
    [0,1,2],// 定义三帧
    [
      0,0,0,//第一帧位置
      5,0,0,//第二帧位置
      0,0,0//第三帧位置
    ]
  )

定义变量clip 并创建动画剪辑
在index.js的顶部定义clip变量

let clip

在initAnimation()中创建动画剪辑

// 动画剪辑
  clip = new THREE.AnimationClip(
    'Action', //动画名称
    4,//动画持续时间
    [moveKeyFrame]//轨迹
  )

上面两步我们分别创建了关键帧和动画剪辑,但是这两个部分是独立的,没有任何关联,我们需要将上面的关键帧和动画剪辑关联起来,这就要用到动画混合器了
创建动画混合器
在index.js的顶部定义mixer变量

let mixer
enableAnimation()

创建enableAnimation()函数,并在该函数中创建动画混合器的实例,该实例接收一个参数,将上面创建的box作为参数传入

function enableAnimation() {
    
    
  // 通过创建动画混合器实例,实现要做动画的物体与动画关联起来
  mixer = new THREE.AnimationMixer( box )
}

执行动画混合器的clipAction()方法,该方法接收一个参数,将上面创建的clip作为参数传入
其返回所传入的剪辑参数的AnimationAction,定义一个变量clipAction 用于接收返回的AnimationAction

 // 通过动画混合器的clipAction方法,实现动画剪辑AnimationClip与动画混合器的关联
  const clipAction =  mixer.clipAction( clip ) 

调用AnimationAction的play方法,执行动画

clipAction.play()

enableAnimation()方法的完整代码如下

function enableAnimation() {
    
    
  // 通过创建动画混合器实例,实现要做动画的物体与动画关联起来
  mixer = new THREE.AnimationMixer( box )
  // 通过动画混合器的clipAction方法,实现动画剪辑AnimationClip与动画混合器的关联
  const clipAction =  mixer.clipAction( clip )
  // 通过上面两步实现 box和clip的关联
  clipAction.play()
}

通过上面的代码,我们已经完成了关键帧定义、动画剪辑创建、动画混合器创建和执行动画的代码,但是,刷新浏览器发现还没有动画过程,这是因为我们还需要将动画混合器在周期处理函数中调用update函数进行更新
在执行update函数时,其接收一个deltaTimeInSeconds 参数,我们先创建一个Threejs内置的时钟对象

let clock = new THREE.Clock()

在animate()方法中定义变量delta 用来接收clock的getDelta()方法返回值,其返回的是自时钟创建开始到现在流失的时间

const delta = clock.getDelta() //获取自 .oldTime 设置后到当前的秒数。

将delta 作为参数传给动画混合器的update方法

// 更新mixer,delta 一个时间的概念
  mixer.update(delta)

animate()方法中的完整代码如下

function animate() {
    
    
  // 获取流失的时间delta
  const delta = clock.getDelta() //获取自 .oldTime 设置后到当前的秒数。
  requestAnimationFrame(animate)
  // 更新mixer,delta 一个时间的概念
  mixer.update(delta)
  renderer.render(scene,camera)
}

至此,我们就实现了物体的移动动画,刷新浏览器,查看效果
在这里插入图片描述

旋转动画

要实现旋转动画,需要先定义沿着哪个轴旋转,并定义旋转的起始角度和终止角度,然后在通过QuaternionKeyframeTrack四元数类型的关键帧轨道来定义关键帧,代码如下

  // 旋转
  const xAxis = new THREE.Vector3(1,0,0) //三维向量,沿x轴
  const qInitial = new THREE.Quaternion().setFromAxisAngle(xAxis,0)//起点角度
  const qFinal = new THREE.Quaternion().setFromAxisAngle(xAxis,Math.PI)//终点角度
  const rotationKeyFrame = new THREE.QuaternionKeyframeTrack(
    '.quaternion',
    [0,1,2],//三帧
    [
      qInitial.x,qInitial.y,qInitial.z,qInitial.w,//第一帧
      qFinal.x,qFinal.y,qFinal.z,qFinal.w,//第二帧
      qInitial.x,qInitial.y,qInitial.z,qInitial.w//第三帧
    ]
  )

定义好关键帧后,将上面定义的关键帧添加到AnimationClip中

// 动画剪辑
  clip = new THREE.AnimationClip(
    'Action', //动画名称
    4,//动画持续时间
    [moveKeyFrame,rotationKeyFrame]//轨迹
  )

刷新浏览器看效果,现在立方体即旋转又移动
在这里插入图片描述
同样的方法,我们可以添加缩放和颜色变化,具体跟上面代码相似,就不在啰嗦了。
ok,这次就写到这里,喜欢的点赞关注收藏哦

猜你喜欢

转载自blog.csdn.net/w137160164/article/details/130222892