分段三次Hermite樣條曲線的應用(Unity 動畫曲線AnimationCurve的實現方法的還原)
阿新 • • 發佈:2019-01-30
分段三次Hermite插值是一種光滑的分段插值。
分段三次Hermite插值函式要滿足的條件:
1. 已知節點(x_i,y_i) 及微商值 k_i (i = 0 , 1, 2, ....... n);
2. 在每個小區間[x_i , x_i_1] 上是不高於三次的多項式。
Unity AnimationCurve動畫曲線是根據一些關鍵幀的節點資訊繪製的一條光滑的曲線。在每個關鍵幀存有節點值及微商值。
AnimationCurve類裡存有關鍵幀的資訊,public Keyframe[] keys;
現在我們根據這些關鍵幀資訊描繪出一條分段三次Hermite樣條曲線的插值函式,看是否與Unity 中的使用的方法一致:
1.先實現函式:
Evaluate 方法為函式主體,根據相鄰2節點的資訊生成一個Hermite曲線函式
using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class UKeyframe { /// <summary> /// Describes the tangent when approaching this point from the previous point in the curve. /// </summary> public float inTangent; /// <summary> /// The out tangent. /// </summary> public float outTangent; /// <summary> /// The time of the keyframe. /// </summary> public float time; /// <summary> /// The value of the curve at keyframe. /// </summary> public float value; public static UKeyframe GetUkeyframe (Keyframe kf) { UKeyframe ukf = new UKeyframe (); ukf.time = kf.time; ukf.value = kf.value; ukf.inTangent = kf.inTangent; ukf.outTangent = kf.outTangent; return ukf; } } public class UAnimationCurve : MonoBehaviour { public UKeyframe[] keys; public void SetKeys (UKeyframe[] keys) { this.keys = keys; } public float Evaluate (float x) { if (this.keys == null || this.keys.Length == 0) { return 0; } return UAnimationCurve.Evaluate (this.keys, x); } /// <summary> /// 分段三次Hermite樣條曲線 P(t) = B1 + B2 * t + B3 * t2 + B4 * t3 /// t 後數字是指數 /// 已知每個節點的x,y 和節點的切線值(導數,微商值) , 可根據相鄰2點和微商值確定一條三次Hermite樣條曲線 /// 注意,當節點數多於2個時,就是分段三次Hermite樣條曲線,每一段的x,y的起點終點 與上一個節點的終點做個偏移(矯正),最後再加上偏移量即可 /// </summary> public static float Evaluate (UKeyframe[] keys, float x) { var index = 0; // 找出當前節點 for (int i = 0; i < keys.Length; i++) { if (i == 0 && x < keys[i].time) { return keys[0].value; } if (x <= keys [i].time) { index = i; if (i == 0) { index = 1; } break; } } if (index == 0) { // index = keys.Length - 1; return keys [keys.Length - 1].value; } // 前一個節點 var startIndex = index - 1; // 後一個節點 var endIndex = index; // 當前時間(當前曲線的時間點) var t = x - keys [startIndex].time; // 當前曲線偏移的時間點 float off_t = keys [startIndex].time; // 當前曲線偏移的值 float off_p = keys [startIndex].value; // 當前曲線起點 var t0 = keys [startIndex].time - off_t; // 當前曲線終點 var t1 = keys [endIndex].time - off_t; // 求引數時用到的是一些表示式 var A = t1 - t0; var B = t1 * t1 - t0 * t0; var C = t1 * t1 * t1 - t0 * t0 * t0; // 起點值(矯正當前曲線的值) var p0 = keys [startIndex].value - off_p; // 終點值(矯正當前曲線的值) var p1 = keys [endIndex].value - off_p; // 起點切線值 var p0_d = keys [startIndex].outTangent; // 終點切線值 var p1_d = keys [endIndex].inTangent; // 求當前曲線引數 var b4 = ((p1 - p0 - p0_d * A) / (B - 2 * A * t0 * t0) - (p1_d - p0_d) / (2 * A)) / ((C - 3 * A * t0 * t0) / (B - 2 * A * t0) - (3 * B / (2 * A))); var b3 = (p1_d - p0_d) / (2 * A) - 3 * B / (2 * A) * b4; var b2 = p0_d - (b3 * 2 * t0 + b4 * 3 * t0 * t0); var b1 = p0 - (b2 * t0 + b3 * t0 * t0 + b4 * t0 * t0 * t0); // 求當前曲線值 var pt = b1 + b2 * t + b3 * t * t + b4 * t * t * t; return pt + off_p; } }
2. 根據現有Unity 曲線關鍵點資訊用此分段三次Hermite樣條函式來臨摹一條曲線
using System.Collections; using System.Collections.Generic; using UnityEngine; public class NewBehaviourScript : MonoBehaviour { /// <summary> /// unity 曲線 /// </summary> public AnimationCurve cure = new AnimationCurve (); void Start () { } void Update () { } void OnDrawGizmos () { if (cure.keys == null || cure.keys.Length == 0) { return; } UKeyframe[] keys = new UKeyframe[cure.keys.Length]; for (int i = 0; i < keys.Length; i++) { keys [i] = UKeyframe.GetUkeyframe (cure.keys [i]); } for (int j = 0; j <= 100; j++) { float s = 1.5f; var pos = new Vector3 (j * 0.01f, UAnimationCurve.Evaluate (keys, j * 0.01f), 0); var pos2 = new Vector3 (j * 0.01f + s, cure.Evaluate (j * 0.01f) + s, 0); Gizmos.DrawCube (pos, Vector3.one * 0.03f); Gizmos.DrawCube (pos2, Vector3.one * 0.03f); } } }
3.把指令碼NewBehaviourScript掛在一個GameObject 上,然後可以在這裡調節一下曲線的樣子
4.在scene 中即可看到通過Unity AnimationCurve自帶函式形成的曲線與我們通過分段三次Hermite插值函式形成的曲線的模樣,可以發現,2條曲線是完全一樣的。