Unity stepping on the pit - multi-level quaternion rotation (multi-level bones)
== It is only for study and notes, please correct me if there is any mistake ==
foreword
The above mentioned that a single object uses a quaternion for local or local rotation. If the object being rotated has sub-objects, and the sub-objects want to be rotated correctly then the handling becomes different again.
It should also be noted that for bones, unlike static models, the displacement and rotation attributes can be frozen in DCC before the static model is imported. But the skeleton does not allow this, so his initial displacement and rotation in the world space itself has values, so it will be more difficult to understand.
Here we still use Euler angles to quaternions for demonstration.
This time to take advantage of this API Transform.localRotation
. Change the global information of the joints of the skeleton into relative information, and then fill in this API.
When a single object becomes a multilevel object. . .
According to the previous section, adding LeftLowerArm
a new rotation (0, 0, 0) here should mean that the joint will not undergo any rotation changes in the joint space, and it is also a world attribute, but in fact, if according to the previous section When thinking about it, an error will occur, as shown in the figure below
public class test : MonoBehaviour
{
Animator animator;
Vector3 upperArm = new Vector3(0f, 0f,67.5f);
Vector3 lowerArm = new Vector3(0f, 0f, 0f);
Quaternion prevQUp;//upper关节原始世界空间的旋转
Quaternion prevQLo;//lower关节原始世界空间的旋转
void Start()
{
// 获取动画控件
animator = this.GetComponent<Animator>();
// 获取原始旋转
prevQUp = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).rotation;
prevQLo = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm).rotation;
//upper的父关节
Quaternion Lshoulder = animator.GetBoneTransform(HumanBodyBones.LeftShoulder).rotation;
//lower的父关节
Quaternion Lelbow= animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).rotation;
//欧拉角转为四元数
Quaternion currentQ1 = Quaternion.Euler(upperArm.x, upperArm.y, upperArm.z);
Quaternion currentQ2 = Quaternion.Euler(lowerArm.x, lowerArm.y, lowerArm.z);
animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).rotation = currentQ1 * prevQUp;
animator.GetBoneTransform(HumanBodyBones.LeftLowerArm).rotation = currentQ2 * prevQLo;
}
}
inspire
At this time, the world space is not very useful. But the abstract of this article gives the answer.
According to the forward dynamics, the world information can be converted into local relative information. The method is to multiply the world rotation inverse of a parent joint to the left. In other words, this operation pulls the information of the joint from the world back to the local.
rotation correction
According to this idea, modify the code
public class test : MonoBehaviour
{
Animator animator;
Vector3 upperArm = new Vector3(0f, 0f,67.5f);
Vector3 lowerArm = new Vector3(0f, 0f, 0f);
Quaternion prevQUp;//upper关节原始世界空间的旋转
Quaternion prevQLo;//lower关节原始世界空间的旋转
void Start()
{
// 获取动画控件
animator = this.GetComponent<Animator>();
// 获取原始旋转
prevQUp = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).rotation;
prevQLo = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm).rotation;
//upper的父关节
Quaternion Lshoulder = animator.GetBoneTransform(HumanBodyBones.LeftShoulder).rotation;
//lower的父关节
Quaternion Lelbow= animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).rotation;
//欧拉角转为四元数
Quaternion currentQ1 = Quaternion.Euler(upperArm.x, upperArm.y, upperArm.z);
Quaternion currentQ2 = Quaternion.Euler(lowerArm.x, lowerArm.y, lowerArm.z);
//animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).rotation = currentQ1 * prevQUp;
//animator.GetBoneTransform(HumanBodyBones.LeftLowerArm).rotation = currentQ2 * prevQLo;
animator.GetBoneTransform(HumanBodyBones.LeftUpperArm).localRotation = Quaternion.Inverse(Lshoulder) * currentQ1 * prevQUp;
//后两项代表全局,第一项代表转换成本地
animator.GetBoneTransform(HumanBodyBones.LeftLowerArm).localRotation = Quaternion.Inverse(Lelbow)* currentQ2 * prevQLo;
}
}