Unity AnimationCurve with Easing Functions
2020-11-22 04:57:00
1.2k words / 4-minute read
目錄 Table of contents
  1. 前言
    1. 準備環境
  2. 實作
  3. Easing Function to Bezier Curve
  4. Bezier Curve to AnimationCurve
    1. Reference
  5. Result
  6. 專案網址

前言

因為專案需求,我需要用 Unity 在 runtime 生成一個 particle system ,並且一些 curve 參數的曲線需要套用 easing function 的曲線。

而 Unity 的曲線一律都是 AnimationCurve ,但 goo 了一下也沒找到有人做過 Easing AnimationCurve ,大概都只有硬拆 N 個 sample 點直接當 AnimationCurve 的 keyframes 這種暴力解。我不喜歡這種方法,理由其一他的使用效能絕對比較爛,再來是結果的形狀也不見得足夠接近。

理論上應該可以找到盡可能小的 keyframe 數量來建構 EasingFunction 的方法,所以就有了這次的嘗試。

準備環境

  • Unity2019.4.12f1 LTS
  • 在Unity能直接使用的EasingFunction類別 EasingFunction.cs by cjddmut

實作

首先我們都知道AnimationCurve的實作就是Cubic Bezier Curve
Cubic Bezier Curve from wiki

所以大方向就是要找方法把Easing Function轉成Bezier Curve的表示法,這方面有很多現成的實作方法與函式庫。

但是AnimationCurve的參數是如何對應到Bezier Curve的,Unity Document沒有給出很明確的解釋。

因此第二步是把BezierCurve轉回AnimationCurve。

然後就大功告成。

Easing Function to Bezier Curve

這裡我翻到了一篇2016的paper Easing Functions in the New Form Based on Bézier Curves by Dariusz Sawicki
他直接提供了所有Easing function以Cubic Bezier來表達的各個係數項與算出來的平方差,完美地解決了我的需求。

但是,我使用的EasingFunction.cs有額外寫了個Spring函數,它不是典型的EasingFunction所以沒有在paper中出現。而我強迫症發作,覺得這個函數必須要一起支援轉換成AnimationCurve才對。

所以還是回歸初心,找一些Bezier Curve Approximation的方法或函式庫來使用。
最後翻到這篇

https://stackoverflow.com/questions/5525665/smoothing-a-hand-drawn-curve

解答者很好心地給了一個quick and dirty C# port of FitCurves的source code,那就直接拿來用囉。

應用就只是在指定的function先暴力sample N個點,然後套這個fitting function來得到Bezier的係數。效果還不錯,但還是paper給的係數比較整齊乾淨,所以最後我也只在spring function上使用這套方法得到的係數。

Bezier Curve to AnimationCurve

這部分網路上稍微有一點討論度,因為Unity Documentation真的寫得很模糊。
我直接給整理吸收完的結論:

  • AnimationCurve上的點稱為Keyframe,代表bezier curve中的p0或p3
  • Keyframe的inTangent, outTangent屬性代表它in(left)和out(right)的斜率,所以它會影響bezier中的p1和p2
  • Keyframe中的inWeight, outWeight屬性代表p1-p0或p3-p2的長度(length or magnitude)除以他們的x距離(p3.x - p1.x)
  • 然後注意inWeight和outWeight需要仰賴weightedMode屬性為Both才會被考慮,否則預設情況WeightedMode.None的情形會使兩個值都固定為0.33333333f(預設p1, p2分別在1/3, 2/3的位置)
  • 所以:
    • p0 = Keyframe0\text{Keyframe0}
    • p1 = Keyframe0’s (outTangentoutWeightlength(Keyframe0.xKeyframe1.x))\text{Keyframe0's}\ (\text{outTangent} * \text{outWeight} * length(\text{Keyframe0.x} - \text{Keyframe1.x}))
    • p2 = Keyframe1’s (inTangentinWeightlength(Keyframe0.xKeyframe1.x))\text{Keyframe1's}\ (\text{inTangent} * \text{inWeight} * length(\text{Keyframe0.x} - \text{Keyframe1.x}))
    • p3 = Keyframe1\text{Keyframe1}
         
      圖示:
      Cubic Bezier Curve to AnimationCurve

所以重點就只是把p1p2轉成正確的Tangent和Weight即可。

Reference

Result

成果我挺滿意的,Keyframe數量落於2~9之間,相比於暴力法用了100個keyframe才能到達差不多的效果。

這裡就隨便挑幾個產出來的AnimationCurve來看看成果:

EaseInOutBounce: 9 keyframes EaseInExpo: 2 keyframes
EaseInOutCubic: 3 keyframes EaseInOutElastic: 9 keyframes

透過移動position來比較一下直接用EasingFunction的效果(GREEN)和AnimationCurve的效果(RED):

效果蠻好的,唯獨Elastic系列的誤差偏大至肉眼可見

這部分我有嘗試過改用Fitting Function求得的係數來比較,效果的確比paper給的係數還要好,但是keyframe數量可能會多達1X個左右。

而我認為如果沒有要求一定要跟原本的elatic比較的話,目前這樣的誤差是完全可以接受的。因為這看起來比較像是彈性相關係數的不一致的些許延遲誤差,他們整體曲線的變化率仍然十分相近。應用上可以直接替換沒問題。

專案網址

包含範例所有的原始碼

https://github.com/qwe321qwe321qwe321/Unity-EasingAnimationCurve

我也另外輸出了一個AnimationCurve的Presets,如果只是想要以AnimationCurve的形式來使用EasingFunction的話可以直接匯入Presets至專案中。