本章介绍threejs的动画及音频效果,用于展示three三维场景中实现动画效果及音频效果的实现处理。

一、学习视频
二、动画
2.1 AnimationAction
AnimationActions 用来调度存储在AnimationClips中的动画。
说明: AnimationAction的大多数方法都可以链式调用
在使用手册的"下一步"章节中,"动画系统"一文对three.js动画系统中的不同元素作出了概述。
构造器
AnimationAction( mixer : AnimationMixer, clip : AnimationClip, localRoot : Object3D )
- mixer - 被此动作控制的 动画混合器
- clip - 动画剪辑 保存了此动作当中的动画数据
- localRoot - 动作执行的根对象
说明: 不要直接调用这个构造函数,而应该先用AnimationMixer.clipAction实例化一个AnimationAction,因为这个方法提供了缓存以提高性能。
属性
- .clampWhenFinished : Boolean
如果 clampWhenFinished 值设为true, 那么动画将在最后一帧之后自动暂停(paused)
如果 clampWhenFinished 值为false, enabled 属性值将在动作的最后一次循环完成之后自动改为false, 那么这个动作以后就不会再执行。
默认值为false
说明: 动作如果被中断了,clampWhenFinished将无效 (只有当最后一次循环执行完毕之后才能起效) - .enabled : Boolean
enabled 值设为false会禁用动作, 也就是无效.默认值是true
当enabled被重新置为true, 动画将从当前时间(time)继续 (将 enabled 置为 false 不会重置此次动作)
说明: 将enabled置为true不会让动画自动重新开始。只有满足以下条件时才会马上重新开始: 暂停(paused)值为false, 同时动作没有失效 (执行停止(stop)命令或重置(reset)命令, 且权重(weight)和时间比例(timeScale)都不能为0 - .loop : Number
循环模式 (可以通过setLoop改变)。默认值是 THREE.LoopRepeat (重复repetitions次数无穷)
必须是以下值之一:
THREE.LoopOnce - 只执行一次
THREE.LoopRepeat - 重复次数为repetitions的值, 且每次循环结束时候将回到起始动作开始下一次循环。
THREE.LoopPingPong - 重复次数为repetitions的值, 且像乒乓球一样在起始点与结束点之间来回循环。 - .paused : Boolean
paused置为true会通过将动作的有效时间比例改为0来使动作暂停执行。默认值是false - .repetitions : Number
整个动作过程动画剪辑(AnimationClip)执行的次数,可以通过setLoop修改。默认值是Infinity
如果循环模式(loop mode)值被设置为THREE.LoopOnce,repetitions值将会无效。 - .time : Number
动作开始的时间点 (单位是秒, 从0开始计时).
这个值被限定在 0到clip.duration(根据循环状态)范围之内. 可以通过改变时间比例(timeScale) (使用 setEffectiveTimeScale 或者 setDuration)来将它相对于全局的混合器缩放. - .timeScale : Number
时间(time)的比例因子. 值为0时会使动画暂停。值为负数时动画会反向执行。默认值是1。
关于timeScale属性/方法 (不同于 time) 有: getEffectiveTimeScale, halt, paused, setDuration, setEffectiveTimeScale, stopWarping, syncWith, warp. - .weight : Number
动作的影响程度 (取值范围[0, 1]). 0 (无影响)到1(完全影响)之间的值可以用来混合多个动作。默认值是1
关于weight的属性/方法有: crossFadeFrom, crossFadeTo, enabled, fadeIn, fadeOut, getEffectiveWeight, setEffectiveWeight, stopFading. - .zeroSlopeAtEnd : Boolean
启用平滑插值,无须单独剪辑开始、循环和结束。默认值是true - .zeroSlopeAtStart : Boolean
启用平滑插值,无须单独剪辑开始、循环和结束。默认值是true
方法
- .crossFadeFrom ( fadeOutAction : AnimationAction, durationInSeconds : Number, warpBoolean : Boolean ) : this
在传入的时间段内,让此动作淡入(fade in),同时让另一个动作淡出。此方法可链式调用。
如果warpBoolean值是true, 额外的 warping (时间比例的渐变)将会被应用。
说明: 与 fadeIn/fadeOut一样, 淡入淡出动作开始/结束时的权重是1. - .crossFadeTo ( fadeInAction : AnimationAction, durationInSeconds : Number, warpBoolean : Boolean ) : this
在传入的时间段内, 让此动作淡出(fade out),同时让另一个动作淡入。此方法可链式调用。
如果warpBoolean值是true, 额外的 warping (时间比例的渐变)将会被应用。
说明: 与 fadeIn/fadeOut一样, 淡入淡出动作开始/结束时的权重是1. - .fadeIn ( durationInSeconds : Number ) : this
在传入的时间间隔内,逐渐将此动作的权重(weight)由0升到1。此方法可链式调用。 - .fadeOut ( durationInSeconds : Number ) : this
在传入的时间间隔内,逐渐将此动作的权重(weight)由1降至0。此方法可链式调用。 - .getEffectiveTimeScale () : Number
返回有效时间比例(考虑当前的变形状态和paused值). - .getEffectiveWeight () : number
返回影响权重(考虑当前淡入淡出状态和enabled的值). - .getClip () : AnimationClip
返回存有此动作的动画数据的剪辑 - .getMixer () : AnimationMixer
返回负责完成此动作的混合器 - .getRoot () : Object3D
返回执行此动作的根对象 - .halt ( durationInSeconds : Number ) : this
在传入的时间间隔内,通过从当前值开始逐渐降低时间比例(timeScale)使动画逐渐减速至0。此方法可链式调用。 - .isRunning () : Boolean
返回当前此动作的时间(time)是否正在流逝
除了在混合器中被激活之外 (参见isScheduled)必须满足以下条件才会返回true: paused值等于false, enabled值为true, timeScale值不为0, 而且没有安排延时启动(startAt).
说明 isRunning 为true并不意味着此动画一定会切实地被看到。仅当权重(weight)被设置为非零值时才是这种情况。 - .isScheduled () : Boolean
如果动作是在混合器中被激活的,返回true。
说明: 这并不意味着动画正在执行中 (需要额外判断isRunning值) - .play () : this
让混合器激活动作。此方法可链式调用。
说明: 激活动作并不意味着动画会立刻开始: 如果动作在此之前已经完成(到达最后一次循环的结尾),或者如果已经设置了延时 启动(通过 startAt),则必须先执行重置操作(reset)。 一些其它的设置项 (paused=true, enabled=false, weight=0, timeScale=0) 也可以阻止动画的开始。 - .reset () : this
重置动作。此方法可链式调用。
该方法会将暂停值 paused 设为false, 启用值enabled 设为true,时间值 time设为0, 中断任何预定的淡入淡出和变形, 以及移除内部循环次数以及延迟启动。
说明: 停止方法stop内调用了重置方法(reset), 但是 .reset不会调用 .stop。 这就表示: 如果你想要这两者, 重置并且停止, 不要调用reset; 而应该调用stop。 - .setDuration ( durationInSeconds : Number ) : this
设置单次循环的持续时间(通过调整时间比例(timeScale)以及停用所有的变形)。此方法可以链式调用。 - .setEffectiveTimeScale ( timeScale : Number ) : this
设置时间比例(timeScale)以及停用所有的变形)。 此方法可以链式调用。
如果暂停 (paused)值为false, 有效的时间比例(一个内部属性) 也会被设为该值; 否则有效时间比例 (直接影响当前动画 将会被设为0.
说明: 如果时间比例.timeScale 被此方法设为0,暂停值paused不会被自动改为true。 - .setEffectiveWeight ( weight : Number ) : this
设置权重(weight)以及停止所有淡入淡出。该方法可以链式调用。
如果启用属性(enabled)为true, 那么有效权重(一个内部属性) 也会被设为该值; 否则有效权重 (直接影响当前动画)将会被设为0.
说明: 如果该方法将权重weight值设为0,启用值enabled不会被自动改为false。 - .setLoop ( loopMode : Number, repetitions : Number ) : this
设置循环(loop mode)及循环重复次数(repetitions)。改方法可被链式调用。 - .startAt ( startTimeInSeconds : Number ) : this
定义延时启动的事件 (通常会传入AnimationMixer.time + deltaTimeInSeconds)。 该方法可以链式调用。
说明: 如果 startAt方法是和play方法一起链式调用, 或者动画已经在混合器中被激活 (通过先行调用play, 同时没有停止或重置),动画将仅在给定时间开始. - .stop () : this
让混合器停止动作。该方法可以被链式调用。
动作会马上停止以及完全[page:.reset 重置].
说明: 你可以通过mixer.stopAllAction在一个混合器中一举停止所有处于激活态的动作。 - .stopFading () : this
停止动作中所有预定的淡入淡出(fading)。该方法可以被链式调用。 - .stopWarping () : this
停用动作中所有预定的变形warping。该方法可被链式调用。 - .syncWith ( otherAction : AnimationAction ) : this
将此动作与传入的其它动作同步。此方法可被链式调用。
同步是通过将动作的时间(time)或时间比例(timeScale)与另一动作设置一致来实现的 (停用所有预定的变形)。
说明: 另一动作的 time 和 timeScale未来变化将不会被检测到. - .warp ( startTimeScale : Number, endTimeScale : Number, durationInSeconds : Number ) : this
在传入的事件间隔内,通过逐渐将时间比例timeScale由startTimeScale修改至endTimeScale来改变回放速度。该方法可被链式调用。
事件
有两个事件分别表示了单次循环的结束和全部动作的结束,你可以这样对它们做出响应: - mixer.addEventListener( 'loop', function( e ) { ...} ); // properties of e: type, action and loopDelta
- mixer.addEventListener( 'finished', function( e ) { ...} ); // properties of e: type, action and direction
typescript
import * as THREE from "three"
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
export default () => {
const canvas = document.getElementById("demo");
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(- 5, 3, 10);
camera.lookAt(0, 2, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xe0e0e0);
scene.fog = new THREE.Fog(0xe0e0e0, 20, 100);
const clock = new THREE.Clock();
// lights
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(0, 20, 10);
scene.add(dirLight);
// ground
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2000, 2000), new THREE.MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }));
mesh.rotation.x = - Math.PI / 2;
scene.add(mesh);
const grid = new THREE.GridHelper(200, 40, 0x000000, 0x000000);
grid.material.opacity = 0.2;
grid.material.transparent = true;
scene.add(grid);
const loader = new GLTFLoader();
loader.load('models/RobotExpressive.glb', function (gltf: { scene: any; animations: any; }) {
const model = gltf.scene;
scene.add(model);
createGUI(model, gltf.animations);
}, undefined, function (e: Error) {
console.error(e);
});
let mixer: THREE.AnimationMixer | null = null;
let gui: GUI | null = null;
interface Action {
[key: string]: any
}
let actions: Action = {};
const api: Action = { state: 'Walking' };
let face = null;
let activeAction: { fadeOut: (duration: number) => void; play: () => void; reset: () => { (): any; new(): any; setEffectiveTimeScale: { (arg0: number): { (): any; new(): any; setEffectiveWeight: { (arg0: number): { (): any; new(): any; fadeIn: { (arg0: number): { (): any; new(): any; play: { (): void; new(): any; }; }; new(): any; }; }; new(): any; }; }; new(): any; }; }; } | null = null;
let previousAction: { fadeOut: (duration: number) => void; play: () => void; reset: () => { (): any; new(): any; setEffectiveTimeScale: { (arg0: number): { (): any; new(): any; setEffectiveWeight: { (arg0: number): { (): any; new(): any; fadeIn: { (arg0: number): { (): any; new(): any; play: { (): void; new(): any; }; }; new(): any; }; }; new(): any; }; }; new(): any; }; }; } | null = null;
function createGUI(model: { getObjectByName: (arg0: string) => any; }, animations: string | any[]) {
const states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing'];
const emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp'];
gui = new GUI();
mixer = new THREE.AnimationMixer(model);
actions = {};
for (let i = 0; i < animations.length; i++) {
const clip = animations[i];
const action = mixer.clipAction(clip);
actions[clip.name] = action;
if (emotes.indexOf(clip.name) >= 0 || states.indexOf(clip.name) >= 4) {
action.clampWhenFinished = true;
action.loop = THREE.LoopOnce;
}
}
// states
const statesFolder = gui.addFolder('States');
const clipCtrl = statesFolder.add(api, 'state').options(states);
clipCtrl.onChange(function () {
fadeToAction(api.state, 0.5);
});
statesFolder.open();
// emotes
const emoteFolder = gui.addFolder('Emotes');
function createEmoteCallback(name: string) {
api[name] = function () {
fadeToAction(name, 0.2);
mixer.addEventListener('finished', restoreState);
};
emoteFolder.add(api, name);
}
function restoreState() {
mixer.removeEventListener('finished', restoreState);
fadeToAction(api.state, 0.2);
}
for (let i = 0; i < emotes.length; i++) {
createEmoteCallback(emotes[i] as string);
}
emoteFolder.open();
// expressions
face = model.getObjectByName('Head_4');
const expressions = Object.keys(face.morphTargetDictionary);
const expressionFolder = gui.addFolder('Expressions');
for (let i = 0; i < expressions.length; i++) {
expressionFolder.add(face.morphTargetInfluences, i, 0, 1, 0.01).name(expressions[i]);
}
activeAction = actions['Walking'];
activeAction?.play();
expressionFolder.open();
}
function fadeToAction(name: string, duration: number) {
previousAction = activeAction;
activeAction = actions[name];
if (previousAction !== activeAction) {
previousAction?.fadeOut(duration);
}
activeAction?.reset()
.setEffectiveTimeScale(1)
.setEffectiveWeight(1)
.fadeIn(duration)
.play();
}
// stats
const stats = new Stats();
document.body.appendChild(stats.dom);
const control = new OrbitControls(camera, renderer.domElement)
control.update();
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
function onPointerMove(event: { clientX: number; clientY: number; }) {
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
function animate() {
control.update();
const dt = clock.getDelta();
if (mixer) mixer.update(dt);
// 通过摄像机和鼠标位置更新射线
raycaster.setFromCamera(pointer, camera);
// 计算物体和射线的焦点
const intersects = raycaster.intersectObjects(scene.children);
for (let i = 0; i < intersects.length; i++) {
intersects[i].object.material.color.set(0xff0000);
}
renderer.render(scene, camera);
}
window.addEventListener('pointermove', onPointerMove);
window.requestAnimationFrame(animate);
}
2.2 AnimationClip
动画剪辑(AnimationClip)是一个可重用的关键帧轨道集,它代表动画。
在使用手册的"下一步"章节中,"动画系统"一文对three.js动画系统中的不同元素作出了概述
构造器
AnimationClip( name : String, duration : Number, tracks : Array )
- name - 此剪辑的名称
- duration - 持续时间 (单位秒). 如果传入负数, 持续时间将会从传入的数组中计算得到。
- tracks - 一个由关键帧轨道(KeyframeTracks)组成的数组。
说明: 与其直接调用构造函数实例化一个动画剪辑, 不如使用其静态方法中的一个来创建: 用JSON (parse), 用变形目标序列(CreateFromMorphTargetSequence, CreateClipsFromMorphTargetSequences)或者用动画层级 (parseAnimation)来创建 - 如果你的模型中几何体的动画数组中还没有保存动画剪辑的话。
属性
- .duration : Number
剪辑的持续时间 (单位秒). 可以通过resetDuration从轨道数组(tracks)计算出来。 - .name : String
剪辑的名称. 调用findByName根据名称搜索某个剪辑 - .tracks : Array
一个包含该剪辑中有动画的所有属性的关键帧轨道(KeyframeTrack)的数组。 - .uuid : String
剪辑实例的UUID,自动分配且不可编辑。
方法
- .clone () : AnimationClip
- .optimize () : this
通过移除等效的顺序键(在变形目标序列中很常见)来优化每一个轨道 - .resetDuration () : this
将剪辑的持续时间(duration)设为最长的关键帧轨道(KeyframeTrack)的持续时间。 - .toJSON () : Object
Returns a JSON object representing the serialized animation clip. - .trim () : this
修剪所有的轨道到该剪辑的持续时间。 - .validate () : Boolean
对剪辑中的每个轨道执行最小验证。如果所有轨道都有效,返回true。
静态方法
-
.CreateClipsFromMorphTargetSequences ( name : String, morphTargetSequence : Array, fps : Number, noLoop : Boolean ) : Array
返回从几何体的变形目标序列(morph target sequences)创建的新动画剪辑(AnimationClip)数组,并尝试将变形目标名称分类为基于动画组的模式,如"Walk_001、Walk_002、Run_001、Run_002......"。
-
.CreateFromMorphTargetSequence ( name : String, morphTargetSequence : Array, fps : Number, noLoop : Boolean ) : AnimationClip
返回一个由几何体变形目标数组(morph targets array)得到的新动画剪辑,接收名称和帧率参数。
说明: 帧率是必须参数, 但是动画速度可能会在AnimationAction中被animationAction.setDuration方法重写。
-
.findByName ( objectOrClipArray : Object, name : String ) : AnimationClip
根据名称搜索动画剪辑(AnimationClip), 接收一个动画剪辑数组或者一个包含名为"animation"的数组的网格(或几何体)作为第一个参数。
-
.parse ( json : Object ) : AnimationClip
解析剪辑的JSON表示,并返回一个动画剪辑(AnimationClip)。
-
.parseAnimation ( animation : Object, bones : Array ) : AnimationClip
解析动画层级格式并返回动画剪辑。
-
.toJSON ( clip : AnimationClip ) : Object
接收一个动画剪辑为参数并返回一个JSON对象.
2.3 AnimationMixer
动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
在使用手册的"下一步"章节中,"动画系统"一文对three.js动画系统中的不同元素作出了概述
构造器
AnimationMixer( rootObject : Object3D )
- rootObject - 混合器播放的动画所属的对象
属性
- .time : Number
全局的混合器时间(单位秒; 混合器创建的时刻记作0时刻) - .timeScale : Number
全局时间(mixer time)的比例因子
说明: 将混合器的时间比例设为0, 稍后再设置为1,可以暂停/取消暂停由该混合器控制的所有动作。
方法
- .clipAction (clip : AnimationClip, optionalRoot : Object3D) : AnimationAction
返回所传入的剪辑参数的AnimationAction, 根对象参数可选,默认值为混合器的默认根对象。第一个参数可以是动画剪辑(AnimationClip)对象或者动画剪辑的名称。
如果不存在符合传入的剪辑和根对象这两个参数的动作, 该方法将会创建一个。传入相同的参数多次调用将会返回同一个剪辑实例。 - .existingAction (clip : AnimationClip, optionalRoot : Object3D) : AnimationAction
返回传入剪辑的已有AnimationAction, 根对象参数可选,默认值为混合器的默认根对象。
第一个参数可以是动画剪辑(AnimationClip)对象或者动画剪辑的名称。 - .getRoot () : Object3D
返回混合器的根对象 - .stopAllAction () : this
停用混合器上所有预定的动作 - .update (deltaTimeInSeconds : Number) : this
推进混合器时间并更新动画
通常在渲染循环中完成, 传入按照混合器的时间比例(timeScale)缩放过的clock.getDelta - .setTime (timeInSeconds : Number) : this
设置全局混合器到一个给定的时间,并相应地更新动画。
当你需要在一个动画里跳转到一个精确的时间,该函数将是十分有用的。输入的参数将会被混合器的timeScale进行缩放。 - .uncacheClip (clip : AnimationClip) : undefined
释放剪辑的所有内存资源 - .uncacheRoot (root : Object3D) : undefined
释放根对象的所有内存资源 - .uncacheAction (clip : AnimationClip, optionalRoot : Object3D) : undefined
释放动作的所有内存资源
2.4 AnimationObjectGroup
接收共享动画状态的一组对象。
在使用手册的"下一步"章节中,"动画系统"一文对three.js动画系统中的不同元素作出了概述
用法:
将本来要作为根对象传入构造器或者动画混合器(AnimationMixer)的clipAction方法中的对象加入组中,并将这个组对象作为根对象传递。
注意,这个类的实例作为混合器中的一个对象,因此,必须对组内的单个对象做缓存控制。
限制
动画属性必须在组中的所有对象之间兼容。
单个属性可以通过目标组控制或者直接控制,但不能两者同时。
构造器
AnimationObjectGroup( obj1 : Object, obj2 : Object, obj3 : Object, ... )
obj - 共享同一动画状态的任意数量的网格
属性
- .isAnimationObjectGroup : Boolean
只读标志,以检查给定对象是否属于 AnimationObjectGroup 类型。 - .stats : Object
一个包含此动画对象组(AnimationObjectGroup)的一些信息的对象 (总数, 使用中的数量,绑定到每个对象上的数量) - .uuid : String
这个动画对象组(AnimationObjectGroup)的UUID。它是自动分配的,不可被编辑。
方法
- .add ( obj1 : Object, obj2 : Object, obj3 : Object, ... ) : undefined
将任意数量的对象添加到这个动画对象组(AnimationObjectGroup)。 - .remove ( obj1 : Object, obj2 : Object, obj3 : Object, ... ) : undefined
将任意数量的对象从这个动画对象组(AnimationObjectGroup)中删除。 - .uncache ( obj1 : Object, obj2 : Object, obj3 : Object, ... ) : undefined
释放此动画对象组(AnimationObjectGroup)传递的对象的所有内存资源
2.5 AnimationUtils
一个提供各种动画辅助方法的对象,内部使用。
方法
-
.convertArray ( array, type, forceClone ) : Array
将数组转换为某种特定类型。
-
.flattenJSON ( jsonKeys, times, values, valuePropertyName ) : Array
用于解析AOS关键帧格式。
-
.getKeyframeOrder ( times ) : Array
返回一个数组,时间和值可以根据此数组排序。
-
.isTypedArray ( object ) : Boolean
如果该对象是类型化数组,返回true
-
.makeClipAdditive ( targetClip : AnimationClip, referenceFrame : Number, referenceClip : AnimationClip, fps : Number ) : AnimationClip
Converts the keyframes of the given animation clip to an additive format.
-
.sortedArray ( values, stride, order ) : Array
将getKeyframeOrder方法返回的数组排序。
-
.subclip ( clip : AnimationClip, name : String, startFrame : Number, endFrame : Number, fps : Number ) : AnimationClip
创建一个新的片段,仅包含所给定帧之间的原始剪辑片段。
2.6 KeyframeTrack
关键帧轨道(KeyframeTrack)是关键帧(keyframes)的定时序列, 它由时间和相关值的列表组成, 用来让一个对象的某个特定属性动起来。
在使用手册的"下一步"章节中,"动画系统"一文对three.js动画系统中的不同元素作出了概述
和JSON model format的动画层级相反, 关键帧轨道(KeyframeTrack)不会将单帧作为对象存储在"key"数组(一个存有每一帧的时间和值的地方)中。
关键帧轨道(KeyframeTrack)中总是存在两个数组:times数组按顺序存储该轨道的所有关键帧的时间值,而values数组包含动画属性的相应更改值。
值数组中的每一个成员,属于某一特定时间点,不仅可以是一个简单的数字,还可以是(比如)一个向量(如果是位置动画)或者是一个四元数(如果是旋转动画)。 因此,值数组(也是一个平面阵列)的长度可能是时间数组的三四倍。
与不同类型的动画值对应,存在若干关键帧轨道(KeyframeTrack)的子类,继承了它大多数属性和方法:
- BooleanKeyframeTrack
- ColorKeyframeTrack
- NumberKeyframeTrack
- QuaternionKeyframeTrack
- StringKeyframeTrack
- VectorKeyframeTrack
可以在AnimationClipCreator文件中找到用不同类型的关键帧轨道创建动画剪辑(AnimationClips)的示例。
由于显式值仅针对存储在时间数组中的离散时间点指定,因此必须在两个时间点之间进行插值
轨道的名称对于这个轨道与动画节点的特定属性的连接(由PropertyBinding完成)很重要。
构造器
KeyframeTrack( name : String, times : Array, values : Array, interpolation : Constant )
- name - 关键帧轨道(KeyframeTrack)的标识符.
- times - 关键帧的时间数组, 被内部转化为 Float32Array.
- values - 与时间数组中的时间点相关的值组成的数组, 被内部转化为 Float32Array.
- interpolation - 使用的插值类型。 参见 Animation Constants for possible values. Default is InterpolateLinear.
属性
- .name : String
轨道的名称可以指动画对象中的变形目标(morph targets)、骨骼(bones)或可能的其他值 查看PropertyBinding.parseTrackName可获知哪些形式的字符串可以解析出绑定的属性:
可以使用节点名称或uuid(尽管它需要位于传递到混合器的场景图节点的子树中)引用到某节点。或者, 如果轨道名称的首字符是点, 该轨道会应用到传入到混合器的根节点上。
通常,在该节点之后会直接指定一个属性。 但是也可以再指定一个子属性, 例如 如果只是想通过浮动轨道使X组件旋转,可使用 .rotation[x]。
还可以使用对象名称来指定骨骼或多材质,例如:.bones[R_hand].scale;再比如,材料数组中的第四个材料的漫反射颜色的红通道可以通过 .materials[3].diffuse[r]访问到。
属性绑定也会解析变形目标名称, 例如: .morphTargetInfluences[run]
说明: 轨道名称不一定得唯一。 多个轨道可驱动统一属性, 此时结果应该基于多个轨道之间根据其各自动作的权重的加权混合。 - .times : Float32Array
一个Float32Array类型的值,由传入构造器中时间数组参数转化而来。 - .values : Float32Array
一个Float32Array类型的值, 由传入构造器中值数组参数转化而来
.DefaultInterpolation : Constant
默认的参数插值类型: InterpolateLinear. - .TimeBufferType : Constant
Float32Array, 内部用于时间数组的缓冲区的类型 - .ValueBufferType : Constant
Float32Array, 内部用于值数组的缓冲区的类型
方法
-
.clone () : KeyframeTrack
-
.createInterpolant () : Interpolant
根据传入构造器中的插值类型参数,创建线性插值(LinearInterpolant),立方插值(CubicInterpolant)或离散插值 (DiscreteInterpolant)
-
.getInterpolation () : Interpolant
返回插值类型
-
.getValueSize () : Number
返回每个值的大小(即values数组的长度除以times数组的长度
-
.InterpolantFactoryMethodDiscrete ( result ) : DiscreteInterpolant
根据时间(times)和值(values)创建一个新的离散插值(DiscreteInterpolant)。 可传入一个Float32Array类型的变量来接收结果, 否则会自动创建一个长度适宜的新数组。
-
.InterpolantFactoryMethodLinear ( result ) : LinearInterpolant
根据时间(times)和值(values)创建一个新的线性插值(LinearInterpolant)。 可传入一个Float32Array类型的变量来接收结果, 否则会自动创建一个长度适宜的新数组。
-
.InterpolantFactoryMethodSmooth ( result ) : CubicInterpolant
根据时间(times)和值(values)创建一个新的立方插值(CubicInterpolant)。 可传入一个 Float32Array类型的变量来接收结果, 否则会自动创建一个长度适宜的新数组。
-
.optimize () : this
删除等效的顺序键,这些键在变形目标序列中很常见。
-
.scale () : this
缩放所有关键帧的时间。
说明: 这个方法很有用,例如, 可用于转化为某一特定帧率(正如animationClip.CreateFromMorphTargetSequence内部所做的一样)。
-
.setInterpolation ( interpolationType : Constant ) : this
设置插值类型。 参阅Animation Constants以供选择。
-
.shift ( timeOffsetInSeconds : Number ) : this
及时删除之前或之后的所有关键帧。
-
.trim ( startTimeInSeconds : Number, endTimeInSeconds : Number ) : this
删除开始时间(startTime)之前以及结束时间(endTime)之后的关键帧,不改变[startTime, endTime]范围内的任何值。
-
.validate () : Boolean
在轨道上执行最小验证,有效则返回true
如果出现以下情况,该方法会在控制台输出错误日志: 轨道为空; value size值不可靠; times数组或values数组中的元素不是数字;times数组中的元素乱序。
静态方法
- .toJSON ( track : KeyframeTrack ) : JSON
将该轨道转化为JSON
2.7 PropertyBinding
对场景图中某一真实属性的引用,内部使用。
构造器
PropertyBinding( rootNode : Object3D, path, parsedPath )
- rootNode: -- path -- parsedPath (可选)
属性
- .path : Number
- .parsedPath : Number
- .node : Number
- .rootNode : Number
- .BindingType : Object
- .Versioning : Object
- .GetterByBindingType : Array
- .SetterByBindingTypeAndVersioning : Array
方法
-
.getValue ( targetArray : Array, offset : Number ) : undefined
-
.setValue ( sourceArray : Array, offset : Number ) : undefined
-
.bind ( ) : undefined
为场景图中的属性创建 getter / setter对。 被getValue和setValue方法内部使用。
-
.unbind ( ) : undefined
解绑场景图中某属性的getter / setter对。
-
.Composite ( targetGroup, path, optionalParsedPath ) : Constructor
创建一个新的复合属性绑定(Composite PropertyBinding)
-
.create ( root, path, parsedPath ) : Constructor
创建一个新的复合属性绑定(Composite PropertyBinding) (如果根对象是AnimationObjectGroup)或普通属性绑定
-
.parseTrackName ( trackName ) : Constructor
匹配以下形式的字符串:
-- nodeName.property
-- nodeName.property[accessor]
-- nodeName.material.property[accessor]
-- uuid.property[accessor]
-- uuid.objectName[objectIndex].propertyName[propertyIndex]
-- parentName/nodeName.property
-- parentName/parentName/nodeName.property[index]
-- .bone[Armature.DEF_cog].position
-- scene:helium_balloon_model:helium_balloon_model.position
-
.findNode ( root, nodeName ) : Constructor
从节点树或骨骼(Skeleton)中找出某节点
2.8 PropertyMixer
允许加权累加的缓冲场景图属性,内部使用
构造器
PropertyMixer( binding : PropertyBinding, typeName : String, valueSize : Number )
- -- binding
- -- typeName
- -- valueSize
属性
- .binding : PropertyBinding
- .buffer : TypedArray
大小为 4 * valueSize 的缓冲区
布局为: [ incoming | accu0 | accu1 | orig ]
插值器可使用 .buffer 作为 .result 且数据会进入'incoming'区. 'accu0'和'accu1'用于按帧交错累加'incoming'中的数据并进行比较以侦测变化。'orig'存储着属性的原始状态。 - .cumulativeWeight : Number
默认值是0 - .valueSize : Number
- .referenceCount : Number
默认值是0 - .useCount : Number
默认值是0
方法
- .accumulate ( accuIndex : Number, weight : Number ) : undefined
将buffer[accuIndex]中'incoming'区的数据累加到'accu[i]'区中。
如果权值为0,则什么都不做。 - .apply ( accuIndex : Number ) : undefined
当累加值不同时,将buffer 'accu[i]区的状态应用于绑定. - .saveOriginalState ( ) : undefined
记住绑定属性的状态并复制到两个'accu'区中. - .restoreOriginalState ( ) : undefined
将预先通过'saveOriginalState'方法取得的状态应用于绑定。
2.9 BooleanKeyframeTrack
布尔类型的关键帧轨道。
构造函数
BooleanKeyframeTrack( name : String, times : Array, values : Array )
- name - (必须) 关键帧轨道(KeyframeTrack)的标识符.
- times - (必须) 关键帧的时间数组.
- values - 与时间数组中的时间点对应的值数组.
属性
参见 KeyframeTrack 查看继承的属性.
- .DefaultInterpolation : Constant
默认的插值类型。 参见 InterpolateDiscrete. - .ValueBufferType : Array
一个基本数组 (不是 Float32Array 类型, 与 KeyframeTrack 内的 ValueBufferType 属性不一样). - .ValueTypeName : String
String 'bool'.
方法
参见 KeyframeTrack 查看继承的方法.
- .InterpolantFactoryMethodLinear () : undefined
这个方法在这里的值为 'undefined', 因为他对离散属性没有意义. - .InterpolantFactoryMethodSmooth () : undefined
这个方法在这里的值为 'undefined', 因为他对离散属性没有意义.
2.10 ColorKeyframeTrack
反应颜色变化的关键帧轨道。
这个子类的基本实现还没有什么特别之处。不过,这里可以表示颜色空间参数化。
构造函数
ColorKeyframeTrack( name : String, times : Array, values : Array )
- name - (必须) 关键帧轨道(KeyframeTrack)的标识符.
- times - (必须) 关键帧的时间数组.
- values - 与时间数组中的时间点对应的值数组.
- interpolation - 使用的插值类型。 取值参考 Animation Constants. 默认值为 InterpolateLinear.
属性
参见 KeyframeTrack 查看继承的属性.
- .ValueTypeName : String
String 'color'.
Methods
参见 KeyframeTrack 查看继承的方法.
2.11 NumberKeyframeTrack
数字类型的关键帧轨道
构造函数
NumberKeyframeTrack( name : String, times : Array, values : Array )
- name - (必须) 关键帧轨道(KeyframeTrack)的标识符.
- times - (必须) 关键帧的时间数组.
- values - 与时间数组中的时间点对应的值数组.
- interpolation - 使用的插值类型。 取值参考 Animation Constants 默认值为 InterpolateLinear.
属性
参见 KeyframeTrack 查看继承的属性.
- .ValueTypeName : String
String 'number'.
方法
参见 KeyframeTrack 查看继承的方法.
2.12 QuaternionKeyframeTrack
四元数类型的关键帧轨道。
构造函数
QuaternionKeyframeTrack( name : String, times : Array, values : Array )
- name - (必须) 关键帧轨道(KeyframeTrack)的标识符.
- times - (必须) 关键帧的时间数组.
- values - 与时间数组中的时间点对应的值数组.
- interpolation - 使用的插值类型。 取值参考 Animation Constants 默认值为 InterpolateLinear.
属性
参见 KeyframeTrack 查看继承的属性.
- .DefaultInterpolation : Constant
默认的插值类型。 参见 InterpolateDiscrete. - .ValueTypeName : String
String 'quaternion'.
方法
参见 KeyframeTrack 查看继承的方法.
- .InterpolantFactoryMethodLinear () : QuaternionLinearInterpolant
根据值数组 (values), 时间 (times) 和值大小 valueSize 创建一个新的线性插值 (QuaternionLinearInterpolant)。
2.13 StringKeyframeTrack
字符串类型的关键帧轨道
构造函数
StringKeyframeTrack( name : String, times : Array, values : Array )
- name - (必须) 关键帧轨道(KeyframeTrack)的标识符.
- times - (必须) 关键帧的时间数组.
- values - 与时间数组中的时间点对应的值数组.
- interpolation - 使用的插值类型。 取值参考 Animation Constants 默认值为 InterpolateDiscrete.
属性
参见 KeyframeTrack 查看继承的属性.
- .DefaultInterpolation : Constant
默认的插值类型。 参见 InterpolateDiscrete. - .ValueBufferType : Array
一个基本数组 (不是 Float32Array 类型, 与 KeyframeTrack 内的 ValueBufferType 属性不一样). - .ValueTypeName : String
String 'string'.
方法
参见 KeyframeTrack 查看继承的方法.
- .InterpolantFactoryMethodLinear () : undefined
这个方法在这里的值为 'undefined', 因为他对离散属性没有意义. - .InterpolantFactoryMethodSmooth () : undefined
这个方法在这里的值为 'undefined', 因为他对离散属性没有意义.
2.14 VectorKeyframeTrack
向量类型的关键帧轨道
构造函数
VectorKeyframeTrack( name : String, times : Array, values : Array )
- name - (必须) 关键帧轨道(KeyframeTrack)的标识符.
- times - (必须) 关键帧的时间数组.
- values - 与时间数组中的时间点对应的值数组.
- interpolation - 使用的插值类型。 取值参考 Animation Constants 默认值为 InterpolateLinear.
属性
参见 KeyframeTrack 查看继承的属性.
- .ValueTypeName : String
String 'vector'.
方法
参见 KeyframeTrack 查看继承的方法.
三、音频
3.1 Audio
创建一个( 全局 ) audio对象.
构造函数
Audio( listener : AudioListener )
listener --- (必须) AudioListener 的实例.
属性
- .autoplay : Boolean
是否自动开始播放. 默认为 false. - .context : AudioContext
构造函数中传入listener的AudioContext. - .detune : Number
修改音高,以音分为单位。 +/- 100为一个半音, +/- 1200为一个八度。默认值为0。 - .filters : Array
表示是一个 AudioNodes 数组。可用于应用各种低阶滤波器来创建更复杂的声音效果。在大多数情况下,该数组包含 BiquadFilterNodes 的实例。通过 Audio.setFilter 或 Audio.setFilters 设置 filter。 - .gain : GainNode
使用AudioContext.createGain()创建的GainNode. - .hasPlaybackControl : Boolean
是否可以使用 play(), pause()等方法控制播放. 默认为 true. - .isPlaying : Boolean
是否正在播放 - .listener : AudioListener
该 audio listener 对象的一个引用 - .playbackRate : Number
播放速率. 默认为 1. - .offset : Number
音频开始播放的偏移时间. 和AudioBufferSourceNode.start()的offset参数一样. 默认为 0. - .duration : Number
覆盖音频的持续时间。与AudioBufferSourceNode.start()中的duration属性相同。默认为undefined,以用于播放整个buffer。 - .source : AudioNode
使用 AudioContext.createBufferSource()创建的AudioBufferSourceNode. - .sourceType : String
音源的类型. 默认为 'empty'. - .type : String
表示类型的字符串, 设置为'Audio'.
方法
- .connect () : this
连接Audio.source. 在初始化和设置/删除filters时使用. - .disconnect () : this
断开连接Audio.source. 在设置/删除filters时使用. - .getFilter () : BiquadFilterNode
返回 filters 数组的第一个元素. - .getFilters () : Array
返回filters 数组. - .getLoop () : Boolean
返回source.loop (是否循环播放)的值. - .getOutput () : GainNode
返回gainNode. - .getPlaybackRate () : Float
返回playbackRate的值. - .getVolume ( value ) : Float
返回音量. - .play ( delay ) : this
如果hasPlaybackControl是true, 开始播放. - .pause () : this
如果hasPlaybackControl是true, 暂停播放. - .onEnded () : undefined
播放完成后自动调用. - .setBuffer ( audioBuffer ) : this
设置source给audioBuffer, 和设置sourceType给'buffer'.
如果autoplay为true, 也开始播放. - .setFilter ( filter ) : this
将单个 filter 节点应用在该 audio 上。 - .setFilters ( value : Array ) : this
value - filter 数组。
将一组 filter 节点应用在该 audio 上。 - .setLoop ( value : Boolean ) : this
设置source.loop的值 (是否循环播放). - .setLoopStart ( value : Float ) : this
设置source.loopStart的值 - .setLoopEnd ( value : Float ) : this
设置source.loopEnd的值 - .setMediaElementSource ( mediaElement ) : this
应用传入的HTMLMediaElement类型对象作为音源。
并且设置hasPlaybackControl为false。 - .setMediaStreamSource ( mediaStream ) : this
应用传入的MediaStream类型对象作为音源。
并且设置hasPlaybackControl为false。 - .setNodeSource ( audioNode ) : this
设置source给audioBuffer, 和设置sourceType给 'audioNode'.
并且设置hasPlaybackControl为false. - .setPlaybackRate ( value : Float ) : this
如果hasPlaybackControl是true, 设置playbackRate(播放速率) 的值. - .setVolume ( value : Float ) : this
设置音量. - .stop () : this
如果hasPlaybackControl是true, 停止播放.
typescript
import * as THREE from "three"
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
const canvas = document.getElementById('demo')
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas })
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(7, 3, 7);
const scene = new THREE.Scene()
// lights
const ambientLight = new THREE.AmbientLight(0xcccccc);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
directionalLight.position.set(0, 5, 5);
scene.add(directionalLight);
const d = 5;
directionalLight.castShadow = true;
directionalLight.shadow.camera.left = - d;
directionalLight.shadow.camera.right = d;
directionalLight.shadow.camera.top = d;
directionalLight.shadow.camera.bottom = - d;
directionalLight.shadow.camera.near = 1;
directionalLight.shadow.camera.far = 20;
directionalLight.shadow.mapSize.x = 1024;
directionalLight.shadow.mapSize.y = 1024;
// floor
const floorGeometry = new THREE.PlaneGeometry(10, 10);
const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x4676b6 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = Math.PI * - 0.5;
floor.receiveShadow = true;
scene.add(floor);
// objects
const count = 5;
const radius = 3;
const ballGeometry = new THREE.SphereGeometry(0.3, 32, 16);
ballGeometry.translate(0, 0.3, 0);
const ballMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc });
const controls = new OrbitControls(camera, renderer.domElement)
//controls.update() must be called after any manual changes to the camera's transform
camera.position.set(0, 20, 100)
controls.update()
// 创建一个 AudioListener 并将其添加到 camera 中
const listener = new THREE.AudioListener();
camera.add(listener);
// 创建一个全局 audio 源
const sound = new THREE.Audio(listener);
// 加载一个 sound 并将其设置为 Audio 对象的缓冲区
const audioLoader = new THREE.AudioLoader();
audioLoader.load('sounds/test.mp3', function (buffer: any) {
sound.setBuffer(buffer);
sound.setLoop(true);
sound.setVolume(0.5);
sound.play();
});
// create an AudioAnalyser, passing in the sound and desired fftSize
// const analyser = new THREE.AudioAnalyser(sound, 32);
// get the average frequency of the sound
// const data = analyser.getAverageFrequency();
// create an object for the sound to play from
const sphere = new THREE.SphereGeometry(20, 32, 16);
const material = new THREE.MeshPhongMaterial({ color: 0xff2200 });
const mesh = new THREE.Mesh(sphere, material);
scene.add(mesh);
// finally add the sound to the mesh
mesh.add(sound);
function animate() {
requestAnimationFrame(animate)
// required if controls.enableDamping or controls.autoRotate are set to true
controls.update()
renderer.render(scene, camera)
}
animate()
}
3.2 AudioAnalyser
创建AudioAnalyser对象, 使用AnalyserNode 去分析音频数据.
构造函数
AudioAnalyser( audio, fftSize )
创建AudioAnalyser.
属性
- .analyser : AnalyserNode
AnalyserNode用来分析音频数据. - .fftSize : Integer
2的幂次方最高为2048, 用来表示确定频域的FFT (傅立叶变换)大小. 这个page有详细信息. - .data : Uint8Array
用来分析数据的Uint8Array的大小由analyser.frequencyBinCount 确定.
方法 - .getFrequencyData () : Uint8Array
使用网络音频的getByteFrequencyData 方法. 看这个页面. - .getAverageFrequency () : Number
通过方法getFrequencyData获取平均频率.
3.3 AudioContext
包含用来设置AudioContext的方法.
- .getContext () : AudioContext
如果定义了,返回给外部context的值, 否则创建一个新的AudioContext. - .setContext ( value : AudioContext ) : AudioContext
外部用来设置 context 的值.
3.4 AudioListener
AudioListener 用一个虚拟的listener表示在场景中所有的位置和非位置相关的音效.
一个three.js程序通常创建一个AudioListener. 它是音频实体构造函数的必须参数,比如 Audio and PositionalAudio.
大多数情况下, listener对象是camera的子对象. Camera的3D变换表示了listener的3D变换.
构造函数
AudioListener( )
创建一个新的AudioListener.
属性
- .context : AudioContext
listener构造函数中的AudioContext. - .gain : GainNode
使用AudioContext.createGain()创建 GainNode. - .filter : AudioNode
默认为null. - .timeDelta : Number
audio 实体的时间差值。在 AudioParam.linearRampToValueAtTimeDefault() 上下文中使用。默认是 0。
方法
- .getInput () : GainNode
返回gainNode. - .removeFilter () : this
设置filter属性为null. - .getFilter () : AudioNode
返回filter属性的值. - .setFilter ( value : AudioNode ) : this
设置filter 属性的值. - .getMasterVolume () : Float
返回音量. - .setMasterVolume ( value : Number ) : this
设置音量.
3.5 PositionalAudio
创建一个位置相关的音频对象.
构造函数
PositionalAudio( listener : AudioListener )
- listener --- (必须) AudioListener 实例.
属性
Audio类的继承属性.
- .panner : PannerNode
位置相关音频的PannerNode.
方法
Audio类的继承方法.
- .getOutput () : PannerNode
返回panner. - .getRefDistance () : Float
返回panner.refDistance的值. - .setRefDistance ( value : Float ) : this
设置panner.refDistance的值. - .getRolloffFactor () : Float
返回panner.rolloffFactor的值. - .setRolloffFactor ( value : Float ) : this
设置panner.rolloffFactor的值. - .getDistanceModel () : String
返回panner.distanceModel的值. - .setDistanceModel ( value : String ) : this
设置panner.distanceModel的值. - .getMaxDistance () : Float
返回panner.maxDistance的值. - .setMaxDistance ( value : Float ) : this
设置panner.maxDistance的值. - .setDirectionalCone ( coneInnerAngle : Float, coneOuterAngle : Float, coneOuterGain : Float ) : this
这个方法用来把环绕声音转换为定向声音directional sound.