こういう質問をされました。
「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にあった。
コメント欄にすごい詳しく書いてた。
なんとなくわかった気がする!
(おまけ)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()
で変化するものが多い場合は検討してもいいかもしれませんね。
それにしても、数学系のことになると、他人の情報ばかりになるなぁ。
自分でも勉強しなくちゃいけませんな!
とりあえず中学数学から復習しようかな……。