
Three.js通过控制模型BlendShapes构建动画
概述
BlendShapes 是一种在计算机图形学中常用的技术,用于实现模型的形状插值和变形。在 Three.js 中,BlendShapes 通常用于实现模型的面部表情动画或者其他形状变换。
在 Three.js 中,您可以通过使用 THREE.Mesh
的 morphTargetInfluences
属性来控制 BlendShapes。每个 BlendShape 被称为一个 "Morph Target",您可以通过调整每个 Morph Target 的影响度来实现模型的形状变换。
示例
以Three.js官方examples为例(演示地址),这是一张可以不断变换表情的人脸,人类的面部表情是成千上万种的,如果使用预制动画的方式,在计算机模型中来模拟人类的面部表情,这将是一个庞大的工程。
BlendShapes的出现就是要将每一个面部动作单独控制,通过不同的组合来实现不同的表情动画。
打开示例我们可以看到右侧的GUI控制栏上的数据在不停的变化,这就是52个BlendShape的值,每一个点位控制一个部位的微表情。 这些点位的名称不是固定的,是预制于每一个模型里的。
代码示例
打开Three.js的源码(地址:GitHub),首先还是创建基础的场景、灯光、相机,这就不一一赘述,然后就是加载了一个压缩的glb模型:
new GLTFLoader()
.setKTX2Loader( ktx2Loader )
.setMeshoptDecoder( MeshoptDecoder )
.load( 'models/gltf/facecap.glb', ( gltf ) => {
} );
加载完成后开始播放模型预制的动画
const mesh = gltf.scene.children[ 0 ];
scene.add( mesh );
mixer = new THREE.AnimationMixer( mesh );
mixer.clipAction( gltf.animations[ 0 ] ).play();
然后将数字记录到GUI面板,这样我们就能只管的看到每一个点位的数值变化
// GUI
const head = mesh.getObjectByName( 'mesh_2' );
const influences = head.morphTargetInfluences;
const gui = new GUI();
gui.close();
for ( const [ key, value ] of Object.entries( head.morphTargetDictionary ) ) {
gui.add( influences, value, 0, 1, 0.01 )
.name( key.replace( 'blendShape1.', '' ) )
.listen();
}
我们看到,在代码中读取了morphTargetInfluences、morphTargetDictionary 字段,这些字段是什么呢?我们看一下模型加载时的mesh数据:
morphTargetDictionary 是Object格式的数据,他代表的就是BlendShapes的数据字典,每一个key就是对应点位的名称,value就是这个点位在morphTargetInfluences的位置。继续看看morphTargetInfluences的数据:
morphTargetInfluences是个长度为52的数组,每一项的数据初始化都为0,这些数据就是控制BlendShape用的,我们刚刚讲过,morphTargetDictionary的每一项的值就是对应此项在morphTargetInfluences数组中的数组下标。这样我们就能通过morphTargetDictionary数据字典,来定位到每一个BlendShape,修改对应的值就可以修改点位的状态,代码如下:
// 假如我们要实现张嘴的动作,这个点位对应的key就是jawOpen,首先获取到
const jawOpenIndex = morphTargetDictionary.jawOpen;
// 然后修改这个值
meth.morphTargetInfluences[jawOpenIndex] = 1;
将jawOpen点位的值赋为1,那么就会发现,模型的嘴巴张开了。
制作动画
这种将点位直接赋值为1的方式是没有过渡效果的,要实现动画功能,我们要逐渐修改这个值,简单的做法为:
let value = 0;
const timer = setInterval(() => {
value += 0.01;
const jawOpenIndex = morphTargetDictionary.jawOpen;
meth..morphTargetInfluences[jawOpenIndex] = value;
if (value >= 1) {
clearInterval(timer);
}
}, 10);
通过定时器,我们可以缓慢的修改值,来达到一个过渡动画的效果。
当然这只是一个简单的思路,Three.js提供了一个定义数值关键帧动画的类:NumberKeyframeTrack,我们可以用这个类来创建一个关键帧动画:
// animationList是一组点位数据
let animationList = [];
// 生成空的动画数组
let animationData = [];
// 创建身体动画数据集
for (
let i = 0;
i < Object.keys(morphTargetDictionary).length;
i++
) {
animationData.push([]);
}
// 解析数据
let time = [];
let finishedFrames = 0;
animationList.forEach((item, i) => {
Object.entries(item).forEach(([key, value]) => {
if (morphTargetDictionary[key] !== undefined) {
animationData[morphTargetDictionary[key]].push(value);
}
});
// 时间线
time.push(finishedFrames / 30);
finishedFrames++;
});
// 构建动画
let tracks = [];
Object.entries(animationList[0]).forEach(([key, value]) => {
if (key in morphTargetDictionary) {
let i = morphTargetDictionary[key];
let track = new THREE.NumberKeyframeTrack(
`mesh.morphTargetInfluences[${i}]`,
time,
animationData[i]
);
tracks.push(track);
}
});
const clip = new THREE.AnimationClip('animation', -1, tracks);
const animateClipAction = animationMixer.clipAction(clip);
// 播放动画
animateClipAction.play();
这里提供一个animationList 数据给以供测试(注意不同模型morphTargetDictionary的key区别)
F5DD2D01-2E92-467D-9BFA-CE921BC2C43F.json
实用意义
我们通过人工智能大模型,将对话的语音转换为BlendShapes点位数据,就可以实现一个真人对讲的人工智能数字人: