使用three.js也能实现3D模型的骨骼绑定,使用代码控制模型!参考这里的呈现效果。
更加常见的应用场景应该是:给一个模型设置多套骨骼动画如唱、跳、Rap等,然后在浏览器中根据用户的输入选择执行不同的动画,这就需要对骨骼动画进行管理,本文先基于three.js官网提供的demo学习其实现机制。
源码解析
这里参考官方提供的demo应用的实现源码,关键代码片段,我已经在代码中作了详细的注释:
const loader = new GLTFLoader();
loader.load( 'models/gltf/Xbot.glb', function ( gltf ) {// 将模型加载到场景中model = gltf.scene;scene.add( model );// 遍历模型,对其中的Mesh添加投射阴影model.traverse( function ( object ) {if ( object.isMesh ) object.castShadow = true;} );// 创建骨骼工具skeleton = new THREE.SkeletonHelper( model );// visible属性是类Object3D的,设置为false将不会被渲染skeleton.visible = false;// 将骨骼工具添加到场景中scene.add( skeleton );// 读取模型中的动画数据const animations = gltf.animations;// 创建AnimationMixer实例mixer = new THREE.AnimationMixer( model );// 读取动画的个数numAnimations = animations.length;// 遍历每一个动画for ( let i = 0; i !== numAnimations; ++ i ) {let clip = animations[ i ];const name = clip.name;/*** 已经预定义了基础动作集合,包括未激活状态、走、跑,这三个动作* const baseActions = {* idle: { weight: 1 },* walk: { weight: 0 },* run: { weight: 0 }* };* 同时也预定义了额外的动作集合:* const additiveActions = {* sneak_pose: { weight: 0 },* sad_pose: { weight: 0 },* agree: { weight: 0 },* headShake: { weight: 0 }* };*/if ( baseActions[ name ] ) {// 如果是基础动作,将其激活并保存到动作集合中const action = mixer.clipAction( clip );// 自定义函数,用于给动作activateAction( action );baseActions[ name ].action = action;allActions.push( action );} else if ( additiveActions[ name ] ) {// 如果是附加的动画,使用AnimationUtils工具类将给定动画剪辑的关键帧转换为附加格式THREE.AnimationUtils.makeClipAdditive( clip );// 这是针对特定动作的定制化需求,可以忽略if ( clip.name.endsWith( '_pose' ) ) {// 创建新剪辑,仅保留2到3帧之间的内容,fps为30,clip = THREE.AnimationUtils.subclip( clip, clip.name, 2, 3, 30 );}const action = mixer.clipAction( clip );activateAction( action );additiveActions[ name ].action = action;allActions.push( action );}}animate();
} );function activateAction( action ) {const clip = action.getClip();const settings = baseActions[ clip.name ] || additiveActions[ clip.name ];setWeight( action, settings.weight );action.play();
}function setWeight( action, weight ) {action.enabled = true;action.setEffectiveTimeScale( 1 );action.setEffectiveWeight( weight );
}
下面是animate()
函数的代码:
function animate() {// Render looprequestAnimationFrame( animate );for ( let i = 0; i !== numAnimations; ++ i ) {const action = allActions[ i ];const clip = action.getClip();const settings = baseActions[ clip.name ] || additiveActions[ clip.name ];settings.weight = action.getEffectiveWeight();}// Get the time elapsed since the last frame, used for mixer updateconst mixerUpdateDelta = clock.getDelta();// Update the animation mixer, the stats panel, and render this framemixer.update( mixerUpdateDelta );stats.update();renderer.render( scene, camera );
}
SkeletonHelper介绍
通过阅读上面的源码, 发现SkeletonHelper是使得模型能够运动起来的关键,在three.js官网中也给出了简短的介绍:
A helper object to assist with visualizing a Skeleton. The helper is rendered using a LineBasicMaterial.
翻译过来就是:这个辅助化工具能够使得骨骼可视化,使用LineBasicMaterial材质进行渲染。
属性
总而言之,这个工具类非常简单,只有如下三个属性:
-
bones : Array
当前模型的骨骼列表,这些骨骼将会以线性的形式渲染出来,如下图:
-
isSkeletonHelper : Boolean
这是一个只读的标志变量,用于检查传入的object是否为SkeletonHelper类型。 -
root : Object3D
该属性保存的是构造函数中传入的模型。
方法
该工具类的方法和 LineSegments类的方法保持一致,说明是继承自 LineSegments类,而 LineSegments是继承自Line类,因此其基本方法就是和Line的绘制相关的方法,这一块直接查看官方文档即可。
AnimationMixer
AnimationMixer能够控制播放模型中包含的动画,如果一个场景中有多个模型存在动画,需要使用多个AnimationMixer分别控制。
The AnimationMixer is a player for animations on a particular object in the scene. When multiple objects in the scene are animated independently, one AnimationMixer may be used for each object.
方法
clipAction (clip : AnimationClip, optionalRoot : Object3D) : AnimationAction
该方法将会返回一个AnimationAction对象。
AnimationClip
动画剪辑(AnimationClip)是一个可重用的关键帧轨道集,它表示一个对象的一个动作。
AnimationAction
AnimationActions 用来调度存储在 AnimationClips 中的动画,它可以管理单个动作,让这个动作开始暂停等。