【Unity】用于Humanoid骨骼的扭曲矫正组件
组件已知限制:
- 可以在一定旋转范围内消除变形,不能无限度的消除变形;
- 不支持连续的两个扭曲矫正骨骼,如果需要,可以自行调整代码。
在Unity中,为了使用骨骼重定向复用动画,需要在模型导入设置中把AnimationType位置为Hunamoid。Humanoid骨骼本身带有一定的限制,比如只支持有限的几个骨骼节点。
骨骼节点过少会导致Animator在播放某些动作时模型发生过度扭曲,为了解决这些问题,通常会增加骨骼数量,来执行扭曲矫正。但在使用Hunamoid骨骼时,这些额外增加的骨骼会被Unity忽略掉,导致扭曲矫正失效。在下图中可以看出,位于Hand与LowerArm之间的1号骨骼和位于LowerArm与UpperArm之间的2号骨骼被Unity忽略掉了。
此时,如果手臂发生大幅度旋转,就会导致手腕附近出现严重的变形。
为了修正模型,可以在脚本中手动执行扭曲矫正,方法也很简单,就是找到两个Humanoid骨骼之间用于扭曲矫正的骨骼,将它的Rotation设置为两个Hunamoid骨骼的Rotation的中间值。
源代码
using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 用于Humanoid骨骼的动画扭曲矫正工具。
/// </summary>
[RequireComponent(typeof(Animator))]
public class HumanoidTwister : MonoBehaviour
{
[Tooltip("扭曲矫正强度。值越小,越贴近父Humanoid骨骼;值越大,越贴近子Humanoid骨骼。")]
[SerializeField]
[Range(0, 1)]
private float _twistIntensity = 0.5f;
[Tooltip("扭曲矫正骨骼数据。")]
[SerializeField]
private List<TwistBoneInfo> _twistBoneInfoList = new List<TwistBoneInfo>
{
new TwistBoneInfo("Hips", "Spine"), new TwistBoneInfo("Spine", "Chest"), new TwistBoneInfo("Chest", "UpperChest"),
new TwistBoneInfo("LeftShoulder", "LeftUpperArm"), new TwistBoneInfo("LeftUpperArm", "LeftLowerArm"), new TwistBoneInfo("LeftLowerArm", "LeftHand"),
new TwistBoneInfo("RightShoulder", "RightUpperArm"), new TwistBoneInfo("RightUpperArm", "RightLowerArm"), new TwistBoneInfo("RightLowerArm", "RightHand"),
new TwistBoneInfo("LeftUpperLeg", "LeftLowerLeg"), new TwistBoneInfo("LeftLowerLeg", "LeftFoot"), new TwistBoneInfo("LeftFoot", "LeftToes"),
new TwistBoneInfo("RightUpperLeg", "RightLowerLeg"), new TwistBoneInfo("RightLowerLeg", "RightFoot"), new TwistBoneInfo("RightFoot", "RightToes")
};
/// <summary>
/// 递归收集Transform的全部节点。
/// </summary>
/// <param name="root"></param>
/// <param name="container"></param>
private void CollectHierarchyRecursively(Transform root, Dictionary<string, Transform> container)
{
container[root.name] = root;
for (int i = 0; i < root.childCount; i++)
{
var child = root.GetChild(i);
CollectHierarchyRecursively(child, container);
}
}
/// <summary>
/// 收集用于扭曲矫正的骨骼节点。
/// </summary>
/// <param name="twistBoneInfo"></param>
/// <param name="humanBones"></param>
/// <param name="actorHierarchy"></param>
private void CollectTwistTransforms(TwistBoneInfo twistBoneInfo, HumanBone[] humanBones, Dictionary<string, Transform> actorHierarchy)
{
string parentHierarchyName = null;
string childHierarchyName = null;
for (int i = 0; i < humanBones.Length; i++)
{
var humanBone = humanBones[i];
var noParentName = string.IsNullOrEmpty(parentHierarchyName);
var noChildName = string.IsNullOrEmpty(childHierarchyName);
// 查找扭曲矫正骨骼的父Humanoid骨骼
if (noParentName && humanBone.humanName.Equals(twistBoneInfo.ParentName))
{
parentHierarchyName = humanBone.boneName;
noParentName = false;
}
// 查找扭曲矫正骨骼的子Humanoid骨骼
if (noChildName && humanBone.humanName.Equals(twistBoneInfo.ChildName))
{
childHierarchyName = humanBone.boneName;
noChildName = false;
}
if (!noParentName && !noChildName)
{
break;
}
}
// Humanoid骨骼不全,退出
if (string.IsNullOrEmpty(parentHierarchyName) || string.IsNullOrEmpty(childHierarchyName))
{
return;
}
// 查找扭曲矫正骨骼(直接连接两个Humanoid骨骼的非Humanoid骨骼)
var parent = actorHierarchy[parentHierarchyName];
var child = actorHierarchy[childHierarchyName];
var twist = child.parent;
if (twist && twist.parent == parent)
{
twistBoneInfo.TwistBone = twist;
twistBoneInfo.ParentBone = parent;
twistBoneInfo.ChildBone = child;
}
else
{
// 没找到匹配的骨骼,标记此数据不合法
twistBoneInfo.SetDisplayNameInvalid();
}
}
private void Reset()
{
// 收集扭曲矫正骨骼
var animatorHierarchy = new Dictionary<string, Transform>(transform.childCount);
CollectHierarchyRecursively(transform, animatorHierarchy);
var avatar = GetComponent<Animator>().avatar;
var humanBones = avatar.humanDescription.human;
for (int i = 0; i < _twistBoneInfoList.Count; i++)
{
var twistInfo = _twistBoneInfoList[i];
CollectTwistTransforms(twistInfo, humanBones, animatorHierarchy);
}
}
private void LateUpdate()
{
// 执行扭曲矫正
for (int i = 0; i < _twistBoneInfoList.Count; i++)
{
_twistBoneInfoList[i].Twist(_twistIntensity);
}
}
/// <summary>
/// 扭曲矫正骨骼数据。
/// </summary>
[Serializable]
class TwistBoneInfo
{
/// <summary>
/// 扭曲矫正骨骼名称。
/// 仅用于Inspector显示。
/// </summary>
[HideInInspector]
public string TwistName;
[Tooltip("扭曲矫正骨骼(直接连接两个Humanoid骨骼的非Humanoid骨骼)。")]
public Transform TwistBone;
/// <summary>
/// 父Humanoid骨骼名称。
/// </summary>
[HideInInspector]
public string ParentName;
[Tooltip("父Humanoid骨骼。")]
public Transform ParentBone;
/// <summary>
/// 子Humanoid骨骼名称。
/// </summary>
[HideInInspector]
public string ChildName;
[Tooltip("子Humanoid骨骼。")]
public Transform ChildBone;
#if !UNITY_EDITOR
/// <summary>
/// 扭曲矫正骨骼信息是否合法。
/// </summary>
private bool? _isValid;
#endif
/// <summary>
/// 构造扭曲矫正骨骼信息。。
/// </summary>
/// <param name="parentName">父Humanoid骨骼名称。</param>
/// <param name="childName">子Humanoid骨骼名称。</param>
public TwistBoneInfo(string parentName, string childName)
{
ParentName = parentName;
ChildName = childName;
TwistName = $"{
ParentName} <-> {
ChildName}";
}
/// <summary>
/// 在扭曲矫正数据的名称中添加非法标识。
/// </summary>
public void SetDisplayNameInvalid()
{
TwistName = $"[INVALID] {
ParentName} <-> {
ChildName}";
}
/// <summary>
/// 检查扭曲矫正数据是否合法。
/// </summary>
/// <returns></returns>
public bool IsValid()
{
// 要求3个骨骼齐全
#if UNITY_EDITOR
return TwistBone && ParentBone && ChildBone;
#else
if (!_isValid.HasValue)
{
_isValid = m_twistBone && m_parentBone && m_childBone;
}
return _isValid.Value;
#endif
}
/// <summary>
/// 执行扭曲矫正。
/// </summary>
/// <param name="intensity">Twist强度。</param>
public void Twist(float intensity)
{
if (!IsValid())
{
return;
}
var childRotation = ChildBone.rotation;
TwistBone.rotation = Quaternion.Slerp(ParentBone.rotation, childRotation, intensity);
ChildBone.rotation = childRotation;
}
}
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(HumanoidTwister))]
class HumanoidTwisterEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// 不应该手动编辑
UnityEditor.EditorGUI.BeginDisabledGroup(true);
base.OnInspectorGUI();
UnityEditor.EditorGUI.EndDisabledGroup();
}
}
#endif
今天的文章【Unity】用于Humanoid骨骼的扭曲矫正组件分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/61669.html