IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【Unity】用于Humanoid骨骼的扭曲矫正组件 -> 正文阅读

[游戏开发]【Unity】用于Humanoid骨骼的扭曲矫正组件

【Unity】用于Humanoid骨骼的扭曲矫正组件

组件已知限制:

  1. 可以在一定旋转范围内消除变形,不能无限度的消除变形;
  2. 不支持连续的两个扭曲矫正骨骼,如果需要,可以自行调整代码。

在Unity中,为了使用骨骼重定向复用动画,需要在模型导入设置中把AnimationType位置为Hunamoid。Humanoid骨骼本身带有一定的限制,比如只支持有限的几个骨骼节点。

Humanoid骨骼所支持的骨骼节点

骨骼节点过少会导致Animator在播放某些动作时模型发生过度扭曲,为了解决这些问题,通常会增加骨骼数量,来执行扭曲矫正。但在使用Hunamoid骨骼时,这些额外增加的骨骼会被Unity忽略掉,导致扭曲矫正失效。在下图中可以看出,位于Hand与LowerArm之间的1号骨骼和位于LowerArm与UpperArm之间的2号骨骼被Unity忽略掉了。

被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
  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-10-23 12:48:29  更:2021-10-23 12:48:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 0:33:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码