人型アニメーションの足 IK ウェイト カーブのベイク処理

導入

以下の図に示すように、キャラクタの足 IK を設定するときにウェイト値を取得するために、2 つのアニメーション カーブ Left および Right FootIK Weight が使用されます。アニメーション クリップが多い場合、手動で編集するのはさらに面倒です。これら 2 つのアニメーション カーブをプログラムでベイク処理することを検討できます。足の IK ウェイト カーブです。
足の IK ウェイト カーブ

実装のアイデア

  • 新しいエディタ ウィンドウを作成し、OnGUI で現在選択されているゲーム オブジェクトを取得し、そのオブジェクト上の Animator アニメーション コンポーネントを取得します。
  • アニメーション コンポーネントでアニメーション クリップ配列を取得し、配列を走査してスクロール ビューでアニメーション クリップをリストし、ウェイト カーブをベイク処理するためのベイク ボタンを追加します。
private Vector2 scroll;
private void OnGUI()
{
    
    
    GameObject selectedGameObject = Selection.activeGameObject;
    if (selectedGameObject == null)
    {
    
    
        EditorGUILayout.HelpBox("未选中任何游戏物体", MessageType.Warning);
        return;
    }
    Animator animator = selectedGameObject.GetComponent<Animator>();
    if (animator == null)
    {
    
    
        EditorGUILayout.HelpBox("所选游戏物体不包含Animator组件", MessageType.Warning);
        return;
    }
    AnimationClip[] clips = animator.runtimeAnimatorController.animationClips;
    if (clips.Length == 0)
    {
    
    
        EditorGUILayout.HelpBox("Animator组件中的动画片段数量为零", MessageType.Warning);
        return;
    }
    scroll = GUILayout.BeginScrollView(scroll);
    for (int i = 0; i < clips.Length; i++)
    {
    
    
        AnimationClip clip = clips[i];
        GUILayout.BeginHorizontal();
        GUILayout.Label(clip.name);
        GUILayout.FlexibleSpace();
        if (GUILayout.Button("Bake", GUILayout.Width(50f)))
            EditorCoroutineUtility.StartCoroutine(BakeCoroutine(selectedGameObject, animator, clip), this);
        GUILayout.EndHorizontal();
    }
    GUILayout.EndScrollView();
}

Humanoid FootIK AnimCurve Baker

  • アニメーション クリップに基づいて、アセット パス、アセット インポーター、および対応する ModelImporterClipAnimation を取得します。ターゲット カーブが既に存在する場合は、再生成のためにフィルタリングします。
//获取资产路径
string assetPath = AssetDatabase.GetAssetPath(clip);
//获取资产导入器
ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
ModelImporterClipAnimation[] clipAnimations = importer.clipAnimations;
ModelImporterClipAnimation target = clipAnimations.FirstOrDefault(m => m.name == clip.name);
//过滤(如果已经有对应的两条曲线)
target.curves = target.curves.Where(m
    => m.name != "Left FootIK Weight"
    && m.name != "Right FootIK Weight").ToArray();
//保存、重新导入
importer.SaveAndReimport();
  • サンプリングは、AnimationClip.SampleAnimation 関数を通じて実行されます (フレーム数 = アニメーション期間 * サンプリング レート)。サンプリングの際、足が地面に着いているかどうかに応じて現在のフレームの値が記録され、足が地面に着いている場合は重みが 1、着地していない場合は重みが 0 となります。初期姿勢はサンプリング前に記録され、サンプリング後に復元されることに注意してください。
//采样率30
int samplingRate = 30;
int frames = Mathf.CeilToInt(clip.length * samplingRate);
Keyframe[] leftFootKeyFrames = new Keyframe[frames];
Keyframe[] rightFootKeyFrames = new Keyframe[frames];
//采样前记录初始姿态
Dictionary<Transform, (Vector3, Quaternion)> pose = new Dictionary<Transform, (Vector3, Quaternion)>();
Transform[] children = animator.GetComponentsInChildren<Transform>();
for (int i = 0; i < children.Length; i++)
{
    
    
    Transform child = children[i];
    pose.Add(child, (child.position, child.rotation));
}
//采样
for (int i = 0; i < frames; i++)
{
    
    
    clip.SampleAnimation(go, (float)i / samplingRate);
    bool isFootGroundedLeft = IsFootGrounded(animator, HumanBodyBones.LeftFoot);
    bool isFootGroundedRight = IsFootGrounded(animator, HumanBodyBones.RightFoot);
    //在地面时权重为1 不在地面时权重为0
    leftFootKeyFrames[i] = new Keyframe((float)i / frames, isFootGroundedLeft ? 1f : 0f);
    rightFootKeyFrames[i] = new Keyframe((float)i / frames, isFootGroundedRight ? 1f : 0f);
    yield return null;
}
//采样后恢复初始姿态
foreach (var kv in pose)
{
    
    
    kv.Key.position = kv.Value.Item1;
    kv.Key.rotation = kv.Value.Item2;
}
  • フィルタリングには、前後のフレームとフレーム値が同じフレームは必要ありません。
//过滤(当帧值与前帧、后帧值都一样)
leftFootKeyFrames = Enumerable.Range(0, frames)
    .Where(i =>
    {
    
    
        bool sameWithPrev = (i - 1) >= 0 && leftFootKeyFrames[i - 1].value == leftFootKeyFrames[i].value;
        bool sameWithLast = (i + 1) < frames && leftFootKeyFrames[i + 1].value == leftFootKeyFrames[i].value;
        return !sameWithPrev || !sameWithLast;
    })
	.Select(i => leftFootKeyFrames[i])
    .ToArray();
rightFootKeyFrames = Enumerable.Range(0, frames)
    .Where(i =>
    {
    
    
        bool sameWithPrev = (i - 1) >= 0 && rightFootKeyFrames[i - 1].value == rightFootKeyFrames[i].value;
        bool sameWithLast = (i + 1) < frames && rightFootKeyFrames[i + 1].value == rightFootKeyFrames[i].value;
        return !sameWithPrev || !sameWithLast;
    })
    .Select(i => rightFootKeyFrames[i])
    .ToArray();
  • 新しく生成された 2 つのアニメーション カーブを追加します。
ClipAnimationInfoCurve leftAnimInfoCurve = new ClipAnimationInfoCurve()
{
    
    
    name = "Left FootIK Weight",
    curve = new AnimationCurve(leftFootKeyFrames)
};
ClipAnimationInfoCurve rightAnimInfoCurve = new ClipAnimationInfoCurve()
{
    
    
    name = "Right FootIK Weight",
    curve = new AnimationCurve(rightFootKeyFrames)
};
//添加生成的两条曲线
target.curves = target.curves.Concat(
    new ClipAnimationInfoCurve[2] {
    
     leftAnimInfoCurve, rightAnimInfoCurve }).ToArray();
importer.clipAnimations = clipAnimations;
importer.SaveAndReimport();

関連ツール

生成プロセスではコルーチンが使用されます。コルーチンは実行時に MonoBehaviour の StartCoroutine を通じて開始でき、ツールはエディター環境で動作します。次の図に示すように、パッケージ マネージャーでコルーチン ツール エディター コルーチンを使用できます。

エディターのコルーチン

おすすめ

転載: blog.csdn.net/qq_42139931/article/details/135198517