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,这次就写到这里,喜欢的点赞关注收藏哦