基础环境搭建:
拷贝官方文件夹RobotExpressive至models/gltf文件夹下
导入外部glb文件并在控制台打印出来,添加到场景中:
场景背景、地面、雾化及添加网格辅助:
GridHelper:坐标格辅助对象,坐标格实际上是2维线数组。
GridHelper( size : number, divisions : Number, colorCenterLine : Color, colorGrid : Color )
morph图形变换:
- 导入GUI,调用控制面板创建函数:
调用控制面板创建函数:
load回调函数...
function (gltf) {
console.log(gltf)
const model = gltf.scene
const clips = gltf.animations
scene.add(model)
mixer = new THREE.AnimationMixer(model)
const face = model.getObjectByName('Head_4')
buildGUI(model, clips, face)
}
...
function buildGUI(model, clips, face) {
gui = new GUI()
}
- 面部表情测试:
console.log(face)
console.log(face.morphTargetDictionary)
console.log(face.morphTargetInfluences)
face.morphTargetInfluences[0] = 1
- 关键变量准备
几个全局变量说明:
let actions = {} //{'动画片段名1':AnimationAction1,'动画片段名2':AnimationAciton2...}
let activeAction, previousAction //当前选中的Action动画动作;上个执行的Action动画动作
let activeActionObject = { state: 'Walking' } //用来操作gui
buildGUI:
const clipNames = [] //动画片段名列表-动画controller
for (let i = 0; i < clips.length; i++) {
const clip = clips[i]
const action = mixer.clipAction(clip)//->return AnimationAction
actions[clip.name]=action//->actions={}
clipNames.push(clip.name)//->clipNames
}
activeAction = actions['Walking']
activeAction.play()
- 创建动画选取调试器:
gui.add(object, property, [min], [max], [step])
向GUI添加一个新的控制器,创建的控制器类型是从对象[property]的初始值推断出来的。有关颜色属性,请参见addColor。
Kind: instance method of GUI
Returns: Controller - The controller that was added to the GUI.
const clipFolder = gui.addFolder("动画")
clipFolder.add(activeActionObject, 'state').options(clipNames).onChange(function () {
const nextActionName = activeActionObject.state
fadeToAction(nextActionName, 0.5) //duratio时间内上个动画片段停止,下个动画片段开始
})
- 动画切换函数:
.fadeIn ( durationInSeconds : Number ) : this
.fadeOut ( durationInSeconds : Number ) : this
在传入的时间间隔内,逐渐将此动作的权重(weight)由0升到1/由1降至0,两方法均可链式调用。
function fadeToAction(nextActionName, duration) {
previousAction = activeAction
activeAction = actions[nextActionName]
if(activeAction!==previousAction){
previousAction.fadeOut(duration)
}
activeAction.reset().fadeIn(duration).play()
}
- 创建表情变换调试器:
Object.keys():获得对象上所有可枚举的实例属性。这个方法接受一个对象作为参数,返回包含该对象所有可枚举属性名称的字符串数组。
const morphFolder = gui.addFolder('表情')
const morphNames = Object.keys(faces.morphTargetDictionary)//表情变换名称列表
for (let i = 0; i < morphNames.length; i++) {
morphFolder.add(faces.morphTargetInfluences, i, 0, 1, 0.01).name(morphNames[i])
}
- 创建复合动作调试器:
const states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing']
const emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp']
const api = {}//{动画片段名:表情切换函数}
const complexFolder = gui.addFolder('穿插')
complexFolder.add(activeActionObject, 'state').options(states).onChange(function () {
const nextActionName = activeActionObject.state
fadeToAction(nextActionName, 0.5)
})
for (let i = 0; i < emotes.length; i++) {
const name = emotes[i]
api[name] = function () {
fadeToAction(name , 0.2)
mixer.addEventListener('finished', restoreState)
}
complexFolder.add(api, emotes[i])
}
function restoreState() {
mixer.removeEventListener('finished', restoreState)
fadeToAction(activeActionObject.state, 0.2)
}
- buildGUI设置循环播放的动作,以及只播放一次的动作(比如出拳、跳、死亡等,只需要播放一次,没必要重复播放进行)
const loopActions = ['Idle', 'Walking', 'Running', 'Dance']
...
if (!loopActions.includes(clip.name)) {
action.clampWhenFinished = true
action.loop = THREE.LoopOnce
}