こういう質問をされました。
「Vector3の関数にLerp()
とSlerp()
というものがあるんですけど、違いって何なんですか」
僕はこう答えました。
「そうだなぁ、SlerpはLerpにSが足されているだろ。LerpよりS(すごい)ってことさ!」
King of 適当な返しです。
若い子には、僕のような駄目な大人にはなって欲しくないな、と思いました。
少しLerp()
とSlerp()
について調べます……。
この記事にはUnity5.5.1f1を使用しています。
- Lerpってなんぞ、Slerpってなんぞ
- LerpとSlerpの違いは?
- Lerp(線形補間)ってどんな補間方法をするの?
- Slerp(球面線形補間)ってどんな補間方法をするの?
- Slerpってどんな時に使うの?
- (おまけ)UnityでLerpやSlerpってどう使うの?
Lerpってなんぞ、Slerpってなんぞ
えーっと、確かLerp()
にしろSlerp()
にしろ、数値αから数値βに変化するとした際に、αからβへの変化途中の補間した数値を取得するものだったかな。
ちなみにほかんは「人類補完計画」のほかんではなくて補間の方だよ。
LerpとSlerpの違いは?
補間する際の計算式が違ったような気がするだよなぁ。
Lerpが線形補間で、Slerpが球面線形補間だったけな。
線形補間はわかりやすいんだけど、球面線形補間がなんか難しかった気が……。
うーん、そうだなぁ。例をあげるとしたら、
大豆から醤油に変化する流れがあるとして、
大豆から醤油になるのが線形補間で、
大豆から味噌を経由して醤油になるのが球面線形補間かなぁ。
……。
うん、絶対違うね。
上の図は見なかったことにしてください。
Lerp(線形補間)ってどんな補間方法をするの?
Wikipedia先生曰く、
線形補間 - Wikipedia
な、なんか小難しい計算式が出てきたなぁ。
数値を直線的に変化させているようですな。
0*が20に変化するとすると、Lerp()
を使って補間値を求めると、3割ぐらい変化したときは数値は6ぐらいになっているでしょうし、半分ぐらい変化したときは10ぐらいになっていることでしょう。
実際Unity上でこの2つのオブジェクトの位置をLerp()
で補間を取ると、以下の画像のようになりました。
ColorにもLerp()
がありますので、そちらも一緒に使って見ましょう。
いい感じですね。
そう、線形補間は何となく分かっているんですよ。
計算式を書けといわれれば、何とか書けますしね。
問題は球面線形補間の方です。
Slerp(球面線形補間)ってどんな補間方法をするの?
とりあえず、先ほどLerp()
で補間をとっていた箇所をSlerp()
に書き換えてみましょう。
どうなるかな。
何この分けわかんない補間…・・・。
この画面はXY平面上のものになっていますが、これをXYZの3D空間で見ると何か分かるのかもしれません、見てみましょう。
おおー、Z軸方向に弧を描いていますね。
まるでコンパス描いたかのような補間をしていますね。
これが球面線形補間なんですね。
ど、どういう計算式なんだろうか。
中学後半以降の数学の授業は基本寝ていた僕にはさっぱりですよ。
こういうときは、頭のいい人が作成しているホームページに頼るしかない!。
というわけで、教えて、○×つくろーどっとコム先生ー!!
マルペケつくろーどっとコム
その57 クォータニオンを"使わない"球面線形補間
さすがですよ。ありましたよ。
ていうか、最初からここを頼れば良かった……。
学生時代から引き続きお世話になります。
Slerpってどんな時に使うの?
で、結局Slerp()
って何に使うのさ。
そもそもLerp()
と違ってSlerp()
はVector3
とQuaternion
ぐらいしかないですからね。
回転系で使いそうですけど。
えーと、何か情報ないかなぁ。
あった、Qiitaにあった。
http://qiita.com/nenjiru/items/ba6ee630b23f0cdd2136qiita.com
コメント欄にすごい詳しく書いてた。
なんとなくわかった気がする!
(おまけ)UnityでLerpやSlerpってどう使うの?
今回は補間挙動を調べる際にも使った、Vector3
とColor
のLerp()
処理をスクリプトで書いてみます。
docs.unity3d.com
docs.unity3d.com
Lerp()
またはSlerp()
を使う際は、変化値を時間で制御する場合と相性がいいですね。
そして、変化後の目標値とは別に開始値も保持してい置く必要があります。
では、早速スクリプトを書いていきます。
using UnityEngine; public class LerpTest : MonoBehaviour { [SerializeField, Range( 0.0f, 10.0f )] private float m_time = 0.0f; [SerializeField] private Vector3 m_targetPosition = Vector3.zero; [SerializeField] private Color m_startColor = Color.white; [SerializeField] private Color m_targetColor = Color.white; private float m_startTime = 0.0f; private Vector3 m_startPosition = Vector3.zero; private Color CurrentColor { set { var render = GetComponent<Renderer>(); if( render == null ) { return; } if( render.material == null ) { return; } render.material.color = value; } } void Start() { m_startTime = Time.time; m_startPosition = transform.position; } void Update() { float timeStep = m_time > 0.0f ? ( Time.time - m_startTime ) / m_time : 1.0f; timeStep = Mathf.Clamp01( timeStep ); transform.position = Vector3.Lerp( m_startPosition, m_targetPosition, timeStep ); CurrentColor = Color.Lerp( m_startColor, m_targetColor, timeStep ); } } // class LerpTest
第三引数に指定する時間は、0.0 ~ 1.0に正規化する必要があります。
今回はMathf.Clamp01()
を使用して、0.0未満、1.0より大きくならないようにしていますが、Lerp()
内でClampしてくれるはずなので必要ないかもしれません。
ClampしないVector3.LerpUnclamped()
なんてのもあります。
実行するとこんな感じです。
[アニメーション]
ただ、メンバ変数として持たないといけない値が多い気がします。
開始時間やら開始位置やら、Lerp()
処理のためだけにこれだけ変数を使うと、ちょっとクラスがごちゃごちゃしてしまいそうですね。
コルーチンと相性がいいかもしれません。
コルーチンを使う形に改良してみましょう。
using UnityEngine; using System.Collections; public class LerpTest : MonoBehaviour { [SerializeField, Range( 0.0f, 10.0f )] private float m_time = 0.0f; [SerializeField] private Vector3 m_targetPosition = Vector3.zero; [SerializeField] private Color m_startColor = Color.white; [SerializeField] private Color m_targetColor = Color.white; void Start() { StartCoroutine( UpdatePosition( transform.position, m_targetPosition, m_time ) ); StartCoroutine( UpdateColor( m_startColor, m_targetColor, m_time ) ); } private IEnumerator UpdatePosition( Vector3 i_startPosition, Vector3 i_targetPosition, float i_time ) { float startTime = Time.time; do { float timeStep = i_time > 0.0f ? ( Time.time - startTime ) / i_time : 1.0f; transform.position = Vector3.Lerp( i_startPosition, i_targetPosition, timeStep ); yield return null; } while( Time.time < startTime + i_time ); } private IEnumerator UpdateColor( Color i_startColor, Color i_targetColor, float i_time ) { var render = GetComponent<Renderer>(); if( render == null ) { yield break; } if( render.material == null ) { yield break; } float startTime = Time.time; do { float timeStep = i_time > 0.0f ? ( Time.time - startTime ) / i_time : 1.0f; render.material.color = Color.Lerp( i_startColor, i_targetColor, timeStep ); yield return null; } while( Time.time < startTime + i_time ); } } // class LerpTest
コルーチンを使うことで、メンバ変数で持っていたものをローカル変数に変更することが出来ました。
同時にLerp()
で変化するものが多い場合は検討してもいいかもしれませんね。
それにしても、数学系のことになると、他人の情報ばかりになるなぁ。
自分でも勉強しなくちゃいけませんな!
とりあえず中学数学から復習しようかな……。