Unity 3D 手部动作捕捉中出现的问题和解决方案

Unity 3D 手部动作捕捉中出现的问题和解决方案

手部动作捕捉

基本实现

手部的位置获取和Unity中的红球绿线的实现是跟着该视频完成的。将手部节点坐标映射到手上还需要一些额外的操作。其中最重要的父子节点相对旋转的代码:

private void UpdateBone(AvatarTree tree, float lerp)
	{
		var dir1 = tree.GetDir();
		var dir2 = pose_joint[tree.idx].transform.position - pose_joint[tree.parent.idx].transform.position;
		//dir2.y = -dir2.y;
		Quaternion rot = Quaternion.FromToRotation(dir1, dir2);
		Quaternion rot1 = tree.parent.transf.rotation;
		
		var lerpToAngle = rot * rot1;
		tree.parent.transf.rotation = Quaternion.Lerp(rot1, lerpToAngle, lerp);
	}

参考了该网站中描述的方法。

问题

在完成上面的步骤后,手部动作的映射依然会遇到比较麻烦的问题。在重复若干次握拳的动作后,手指发生如下图中的扭曲。
手部扭曲

估计原因

由于该方法是根据两个手部节点的坐标(就是场景中相邻的两个小球)来计算出旋转的Quaternion的。这样得出的Rotation在x和z轴的旋转都是没有问题的,但是在y轴的旋转就有很多问题了。我们的代码无法判断出节点(红色小球)的本地y轴(object.transf.localEulerAngles.y)的角度是多少(它可以是任何值,因为都符合相邻两个小球的旋转)。解决该问题的原则是让节点的本地y坐标不转,全局y轴会跟随父节点转动。我尝试过很多方法来解决这个问题,但是都会出现新的问题。比如当我想通过强制手部节点的transfrom.right和手腕的transfrom.right相同时,手模型会无法控制,因为在Unity中改变transfrom.right时,所有轴上的旋转都会被改变,这个问题在该up的文章中也有所描述;当我想通过限定lerpToAngle.y = 0来固定指定节点(主要是除了大拇指的指见关节)不会沿着y轴旋转时,手部一面是正常的,但转到另外一个面时会完全旋转180°;当我想限制节点本地的y坐标总是为0时,tree.parent.transf.localEulerAngles = new Vector3(angle.x, 0, angle.z) 当手部握拳超过90°,手指会进行一个很鬼畜地内面转外面(这个效果相对还不错了),原因类似于第一个问题。握拳90°的状态如下,继续握拳为大于90°,展开手掌为小于90°。
握拳90°

我的解决方案

我最终的处理方法其实就是在上文中最后一个尝试的基础上加一个限定if(Vector3.Dot(tree.parent.transf.up, Wrist.transform.up) > 0) 只有当手指节点的transform.up与手腕节点的transform.up之间的夹角小于90度时才限制local坐标y-axis为0。这样就不会在手部握拳超过90°时出现鬼畜旋转了。这个方法不是最完美的,因为在手指握拳超过90°后还是会发生微小的不规则的本地y轴旋转,但是这种旋转不会像之前一样累加了,因为每当握拳幅度小于90°后代码会强制让本地y轴归零。虽然这个方法感觉上不太优雅,逻辑也不太完美,但是至少视觉上可以看了,不会出现太鬼畜的扭曲[笑哭.jpg]。

如果大佬们能看懂我写的文章,并且能探索出更好的方法欢迎分享出来。手部捕捉节点骨骼绑定和更新的完整代码如下:

using System.Collections.Generic;
using UnityEngine;

public class AvatarJoint : JointBase
{
	private int[] fixedRotationY = {5,6,7,9,10,11,13,14,15,17,18,19};

	public class AvatarTree
	{
		public Transform transf;
		public AvatarTree[] childs;
		public AvatarTree parent;
		public int idx;  // pose_joint's index

		public AvatarTree(Transform tf, int count, int idx, AvatarTree parent = null)
		{
			this.transf = tf;
			this.parent = parent;
			this.idx = idx;
			if (count > 0)
			{
				childs = new AvatarTree[count];
			}
		}

		public Vector3 GetDir()
		{
			if (parent != null)
			{
				return transf.position - parent.transf.position;
			}
			return Vector3.up;
		}
	}

	private AvatarTree wrist, thumb_cmc, thumb_mcp, thumb_ip, thumb_tip, index_finger_mcp, index_finger_pip, index_finger_dip, index_finger_tip, middle_finger_mcp, middle_finger_pip;
	private AvatarTree middle_finger_dip, middle_finger_tip, ring_finger_mcp, ring_finger_pip, ring_finger_dip, ring_finger_tip, pinky_mcp, pinky_pip, pinky_dip, pinky_tip;
	
	private int[] L = {3, 4, 7, 8, 11, 12, 15, 16, 19, 20};
	private List<int> limitYRotationList;

	protected override float speed { get { return 10f; } }

	void Start()
	{
		limitYRotationList = new List<int>(L);
		InitData();
		BuildTree();
	}

	void BuildTree()
	{
		wrist = new AvatarTree(Wrist.transform, 5, 0);
		thumb_cmc = wrist.childs[0] = new AvatarTree(Thumb_CMC.transform, 1, 1, wrist);
		index_finger_mcp = wrist.childs[1] = new AvatarTree(Index_Finger_MCP.transform, 1, 5, wrist);
		middle_finger_mcp = wrist.childs[2] = new AvatarTree(Middle_Finger_MCP.transform, 1, 9, wrist);
		ring_finger_mcp = wrist.childs[3] = new AvatarTree(Ring_Finger_MCP.transform, 1, 13, wrist);
		pinky_mcp = wrist.childs[4] = new AvatarTree(Pinky_MCP.transform, 1, 17, wrist);

		thumb_mcp = thumb_cmc.childs[0] = new AvatarTree(Thumb_MCP.transform, 1, 2, thumb_cmc);
		thumb_ip = thumb_mcp.childs[0] = new AvatarTree(Thumb_IP.transform, 1, 3, thumb_mcp);
		thumb_tip = thumb_ip.childs[0] = new AvatarTree(Thumb_TIP.transform, 0, 4, thumb_ip);

		index_finger_pip = index_finger_mcp.childs[0] = new AvatarTree(Index_Finger_PIP.transform, 1, 6, index_finger_mcp);
		index_finger_dip = index_finger_pip.childs[0] = new AvatarTree(Index_Finger_DIP.transform, 1, 7, index_finger_pip);
		index_finger_tip = index_finger_dip.childs[0] = new AvatarTree(Index_Finger_TIP.transform, 0, 8, index_finger_dip);

		middle_finger_pip = middle_finger_mcp.childs[0] = new AvatarTree(Middle_Finger_PIP.transform, 1, 10, middle_finger_mcp);
		middle_finger_dip = middle_finger_pip.childs[0] = new AvatarTree(Middle_Finger_DIP.transform, 1, 11, middle_finger_pip);
		middle_finger_tip = middle_finger_dip.childs[0] = new AvatarTree(Middle_Finger_TIP.transform, 0, 12, middle_finger_dip);

		pinky_pip = pinky_mcp.childs[0] = new AvatarTree(Pinky_PIP.transform, 1, 18, pinky_mcp);
		pinky_dip = pinky_pip.childs[0] = new AvatarTree(Pinky_DIP.transform, 1, 19, pinky_pip);
		pinky_tip = pinky_dip.childs[0] = new AvatarTree(Pinky_TIP.transform, 0, 20, pinky_dip);

		ring_finger_pip = ring_finger_mcp.childs[0] = new AvatarTree(Ring_Finger_PIP.transform, 1, 14, ring_finger_mcp);
		ring_finger_dip = ring_finger_pip.childs[0] = new AvatarTree(Ring_Finger_DIP.transform, 1, 15, ring_finger_pip);
		ring_finger_tip = ring_finger_dip.childs[0] = new AvatarTree(Ring_Finger_TIP.transform, 0, 16, ring_finger_dip);
	}


	protected override void LerpUpdate(float lerp)
	{
		Wrist.transform.position = pose_joint[0].transform.position - new Vector3(5,0,0);
		//Wrist.transform.rotation = pose_joint[0].transform.rotation;
		//UpdateBone(thumb_cmc, lerp);
		UpdateBone(index_finger_mcp, lerp);
		//UpdateBone(pinky_mcp, lerp);
		//UpdateBone(thumb_mcp, lerp);
		UpdateBone(thumb_ip, lerp);
		UpdateBone(thumb_tip, lerp);
		UpdateBone(index_finger_pip, lerp);
		UpdateBone(index_finger_dip, lerp);
		UpdateBone(index_finger_tip, lerp);
		UpdateBone(middle_finger_mcp, lerp);
		UpdateBone(middle_finger_pip, lerp);
		UpdateBone(middle_finger_dip, lerp);
		UpdateBone(middle_finger_tip, lerp);
		UpdateBone(pinky_pip, lerp);
		UpdateBone(pinky_dip, lerp);
		UpdateBone(pinky_tip, lerp);
		UpdateBone(ring_finger_mcp, lerp);
		UpdateBone(ring_finger_pip, lerp);
		UpdateBone(ring_finger_dip, lerp);
		UpdateBone(ring_finger_tip, lerp);
	}


	private void UpdateTree(AvatarTree tree, float lerp)
	{
		if (tree.parent != null)
		{
			UpdateBone(tree, lerp);
		}
		if (tree.childs != null)
		{
			for (int i = 0; i < tree.childs.Length; i++)
				UpdateTree(tree.childs[i], lerp);
		}
	}

	private void UpdateBone(AvatarTree tree, float lerp)
	{
		var dir1 = tree.GetDir();
		var dir2 = pose_joint[tree.idx].transform.position - pose_joint[tree.parent.idx].transform.position;
		//dir2.y = -dir2.y;
		Quaternion rot = Quaternion.FromToRotation(dir1, dir2);
		Quaternion rot1 = tree.parent.transf.rotation;

		#region Recover rotation
		var lerpToAngle = rot * rot1;

		//local y坐标不转,globally y轴会跟随父节点转动	
		tree.parent.transf.rotation = Quaternion.Lerp(rot1, lerpToAngle, lerp);
		
		foreach (int n in fixedRotationY)
		{
		   if (tree.parent.idx == n)
		   {
			   	var angle =  tree.parent.transf.localEulerAngles;
				//当手指节点的transform.up与手腕节点的transform.up之间的的夹角小于90度时限制local坐标y-axis为0
			   	if(Vector3.Dot(tree.parent.transf.up, Wrist.transform.up) > 0)
			   		tree.parent.transf.localEulerAngles = new Vector3(angle.x, 0, angle.z);			   
		   }
		}
		#endregion
	}
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity3D是一款流行的游戏开发引擎,提供了丰富的资源和工具来创建各种类型的游戏。在Unity3D,走路动作资源是非常重要的,它们可以用来使游戏的角色或者角色模型进行自然而流畅的移动。 Unity3D提供了多种走路动作资源的创建和使用方式。首先,我们可以使用Unity3D内置的动画编辑器来创建走路动作资源。这个编辑器允许开发者通过记录和编辑角色的关键帧来制作动画。通过调整关键帧的位置、旋转和缩放等参数,我们可以创造出逼真的走路动作。 其次,Unity3D还支持导入外部的3D动画资源。开发者可以使用第三方软件如Blender或者Maya来制作走路动作,并将其导出为常见的文件格式如FBX。然后,我们可以通过Unity3D的导入工具将这些外部动画资源导入到游戏项目,并将其应用到游戏角色上。这样做可以节省开发时间,同时也提供了更多自定义和调整的可能性。 除了以上的方法,Unity3D还提供了一些开源的走路动作资源库。这些资源库包含了大量的预制的走路动作,可以直接在游戏项目使用,无需制作新的动画。开发者可以根据自己的需求选择适合的动作资源。 总结起来,Unity3D提供了多种走路动作资源的创建和使用方式,包括内置的动画编辑器、导入外部动画资源以及开源的走路动作资源库。通过这些资源和工具,开发者可以轻松创建出流畅逼真的走路动画,为游戏增加更多的真实感和沉浸感。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值