Unity 设置目标IK的position和rotation
使用Unity官方自带的 IK例子, 资源地址下载,IK的目标rotation会有问题。
动画状态机设置如下:
第0层是一个动画层,包含idle,walk,跳跃等动画。
第1层是左手遮罩层次,是一个左手的pose
以左手为例子,
IK代码
using UnityEngine;
using System;
using System.Collections;
[RequireComponent(typeof(Animator))]
public class IKTest : MonoBehaviour
{
protected Animator animator;
public bool ikActive = false;
public Transform LeftHandObj = null;
public Transform lookObj = null;
void Start()
{
animator = GetComponent<Animator>();
}
//a callback for calculating IK
void OnAnimatorIK()
{
if (animator)
{
//if the IK is active, set the position and rotation directly to the goal.
if (ikActive)
{
// Set the look target position, if one has been assigned
if (lookObj != null)
{
animator.SetLookAtWeight(1);
animator.SetLookAtPosition(lookObj.position);
}
// Set the right hand target position and rotation, if one has been assigned
if (LeftHandObj != null)
{
animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1);
animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);
animator.SetIKPosition(AvatarIKGoal.LeftHand, LeftHandObj.position);
animator.SetIKRotation(AvatarIKGoal.LeftHand, LeftHandObj.rotation);
}
}
//if the IK is not active, set the position and rotation of the hand and head back to the original position
else
{
animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 0);
animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 0);
animator.SetLookAtWeight(0);
}
}
}
void LateUpdate()
{
Debug.DrawRay(LeftHandObj.position, LeftHandObj.forward * 10, Color.green);
Debug.DrawRay(animator.GetBoneTransform(HumanBodyBones.LeftHand).position, animator.GetBoneTransform(HumanBodyBones.LeftHand).forward * 10, Color.red);
}
}
设置左手的目标position和rotation,发现虽然位置和targe的position一样,但是旋转和target的旋转不一样。绿色为期望的方向,红色为左手当前的方向。如图:
google搜索了一阵子,一个解决方案是在LateUpdate中直接设置左手旋转,不使用Unity的SetIKRotation
void LateUpdate()
{
animator.GetBoneTransform(HumanBodyBones.LeftHand).rotation =
LeftHandObj.rotation;
Debug.DrawRay(LeftHandObj.position, LeftHandObj.forward * 10, Color.green);
Debug.DrawRay(animator.GetBoneTransform(HumanBodyBones.LeftHand).position, animator.GetBoneTransform(HumanBodyBones.LeftHand).forward * 10, Color.red);
}
如下图:
但是这个不是通过IK旋转出来的结果,可能会有问题。
还有一个解决方法:
Unity dev的一个解决方案:
There is effectively a constant rotation offset between original skeleton bone rotation and mecanim IK goal rotation.
Goal Rotation = GR
Skeleton Rotation = SR
Offset Rotation = OR
where:
GR = SR * OR
or:
OR = INV(SR) * GR
You can compute this constant offset in the 1st IK pass since IK goals are synced to skeleton at the beginning of IK solve.
GR = GetGoalRotation(handGoalIndex)
SR = GetBoneTransform(handIndex).rotation
Then you can use OR to set a goal within skeleton referential using:
SetIKGoalRotation(SomeRot*OR)
Note: Mecanim Humanoid abstracts the original skeleton rig for many reason. One of these is to be able to create IK rigs that do not depend on original skeleton, but instead only depend a normalized humanoid rig that fits all humanoid characters.
意思是IK Rotation会有一个固定的旋转offset,导致了目标旋转和左手当前的旋转不一致,按照上面的思路,添加一个offset。对于idle动画是正确的,但是对于奔跑动画旋转还是不对,效果如下:
对于idle动画是正确的,播放奔跑时候也会有偏差!。
代码:
if (LeftHandObj != null)
{
animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1);
animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);
var gr = animator.GetIKRotation(AvatarIKGoal.LeftHand);
var sr = animator.GetBoneTransform(HumanBodyBones.LeftHand).rotation;
var or = Quaternion.Inverse(sr) * gr;
animator.SetIKPosition(AvatarIKGoal.LeftHand, LeftHandObj.position);
animator.SetIKRotation(AvatarIKGoal.LeftHand, LeftHandObj.rotation * or);
}
播放奔跑效果不对,最后发现有二种解决方法,
- 是BaseLayer不勾选IK Pass, HandPose的mask 左右手不点上IK,实际效果旋转只有一点点的偏差
- 是BaseLayer勾选IK Pass, HandPose的mask 左右手点上IK,实际效果完美,具体原因未知,应该是Unity对IK的实现有问题。
demo下载地址:https://download.csdn.net/download/a352614834/10520538