Unity 运行模型
将上一步Python环境里生成的关键点信息(二进制文件),导入到Unity里,使用IO接口解析出来,由于Video3DPose默认使用的[hunman3.6数据集]骨骼的标记是17个部位,这里我们创建了17个GameObject,代表17个关节节点, 并使用LineRender组件把相关的关节连接成线。在Mono的Update函数,我们更新导出模型的3d关键点位置信息,于是一个运动的骨骼人行动起来了。 关于[hunman3.6数据集]关节编号的详细介绍,见本文最后的附录。
代码如下, 在Update中插值更新17组关节的位置,pose_joint数组的顺序对应到hunman3.6数据集关节编号。
protected override void LerpUpdate(float lerp)
{ Hip.position = Vector3.Lerp(Hip.position, pose_joint[0], lerp); RHip.position = Vector3.Lerp(RHip.position, pose_joint[1], lerp); RKnee.position = Vector3.Lerp(RKnee.position, pose_joint[2], lerp); RFoot.position = Vector3.Lerp(RFoot.position, pose_joint[3], lerp); LHip.position = Vector3.Lerp(LHip.position, pose_joint[4], lerp); LKnee.position = Vector3.Lerp(LKnee.position, pose_joint[5], lerp); LFoot.position = Vector3.Lerp(LFoot.position, pose_joint[6], lerp); Spine.position = Vector3.Lerp(Spine.position, pose_joint[7], lerp); Thorax.position = Vector3.Lerp(Thorax.position, pose_joint[8], lerp); Neck.position = Vector3.Lerp(Neck.position, pose_joint[9], lerp); Head.position = Vector3.Lerp(Head.position, pose_joint[10], lerp); LShoulder.position = Vector3.Lerp(LShoulder.position, pose_joint[11], lerp); LEblow.position = Vector3.Lerp(LEblow.position, pose_joint[12], lerp); LWrist.position = Vector3.Lerp(LWrist.position, pose_joint[13], lerp); RShoulder.position = Vector3.Lerp(RShoulder.position, pose_joint[14], lerp); REblow.position = Vector3.Lerp(REblow.position, pose_joint[15], lerp); RWrist.position = Vector3.Lerp(RWrist.position, pose_joint[16], lerp);
}
怎么能使游戏的Avatar的3D模型也能像输入模型那样运动起来呢? 换句话说,如何求一种解法将上一步的人形转换为游戏的绑定顶点的骨骼。我们知道3D角色的骨骼动画都是在CPU端层级计算的,上一级关节是下一节关节的原点坐标,由于关节之间的部位长度是固定的,于是问题转换成了求父关节的旋转,即四元数。当父关节的旋转固定的时候,子关节的位置随即也确定了。
对于旋转的求法, 先根据父子关节关系求一个角度, 然后在根据关键点模型里的位置算一个角度, 使用Quaternion.FromToRotation得到一个旋转四元数, 然后在当前的基础上旋转得到目标四元数,最后在Update里由当前插值到目标四元数就行了。代码实现如下:
private void UpdateBone(AvatarTree tree, float lerp)
{ var dir1 = tree.GetDir(); var dir2 = pose_joint[tree.idx] - pose_joint[tree.parent.idx]; Quaternion rot = Quaternion.FromToRotation(dir1, dir2); Quaternion rot1 = tree.parent.transf.rotation; tree.parent.transf.rotation = Quaternion.Lerp(rot1, rot * rot1, lerp);
}
unity里运行效果如图所示:
如果想看更多的运行效果,参考附录视频。
TIP
一般的情况下游戏里的Avatar关节和Human3.6数据集里的骨骼不是匹配的,往往游戏或者3D Max等美术制作的工具里的骨骼设置的更多,这时候可以跟动作设计师提出跟Hum3.6数据集的骨骼蒙皮解决方案。 如果是已经导出的fbx,我们也得保证关节层级的对应,应该上述算法得到的旋转四元数是基于父节点的local坐标系的。 解决方法就是把fbx在Unity引擎转换为Prefab, 然后再更改Prefab的关节层级结构。
从上面视频我们可以看到, AlphaPose 虽然能很好的识别出骨骼节点的位置, 但这不包含手指等小关节的姿势, 所以从上而下手都是僵硬的, 但是从卡内基梅隆大学的OpenPose(最新版)已经能识别手指的动作,甚至还包含一些脸部的表情,真的是非常强大了。