1. 程式人生 > >分段三次Hermite樣條曲線的應用(Unity 動畫曲線AnimationCurve的實現方法的還原)

分段三次Hermite樣條曲線的應用(Unity 動畫曲線AnimationCurve的實現方法的還原)

分段三次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條曲線是完全一樣的。