Quaternionクラスのメモ
Quaternion.FromToRotation関数
fromDirection引数からtoDirection引数に回転軸を作成する
クォータニオンの回転差分を作れると考えていい。
Vector3 player,enemy; void Start () { player = Vector3.forward; enemy = new Vector3(1,0,1); print(Quaternion.FromToRotation(player,enemy).eulerAngles); }
(0.0 , 45.0 , 0.0)
Vector3.angle関数
2点間の角度を返す(相対的な角度(絶対数)を求める)
原点と点を結ぶ2本線の角度差が絶対値のfloatで出力される
クォータニオンの差分で出力されるわけでないので、返した値を利用して物体を回転させる事は出来ない点に注意
例えば、敵が前方左右35度以内にプレーヤーを捕らえているか等の角度の差の値を比較する用途に使えると思われる
クォータニオンは掛ける順番によって結果が変わる
例.掛ける順番を変えた結果を見比べるコード
この事からクォータニオンは値をかけた際、常にオブジェクトのローカル軸を基準にして相対的に回転操作(変換)が加えられていることが理解できる
相対的であるため順番が変わると結果が変わる
- 右斜め上に向いた状態からローカル軸を基準に右に45度回転
- 右に45度回転した状態からローカル軸を基準に右斜め上に向く
前者は左に体が傾いた状態になるが、後者は傾かない
using UnityEngine;
using System.Collections;
public class RotTest : MonoBehaviour
{
Transform _tr; bool toggle; void Start () { _tr = transform; } void Update () { if (Input.GetButtonDown ("Jump")) { toggle = !toggle; } if (toggle) { _tr.rotation = Quaternion.LookRotation (new Vector3 (1, 1, 1)) * Quaternion.AngleAxis (45, Vector3.up); } else { _tr.rotation = Quaternion.AngleAxis (45, Vector3.up) * Quaternion.LookRotation (new Vector3 (1, 1, 1)); } }
}
チュートリアルの学習(その3)
第二引数で設定したベクトルを軸に第一引数の角度(単位は度)分回転するクォータニオン成分を作成する
第一引数の方向を第二引数の方向へ向けるクォータニオン成分を作成する
チュートリアルの学習(その2)
オブジェクトのTransformをカメラ方向に向けさせる方法
手軽に向けさせるならこれが良い
void Update () { transform.LookAt(Camera.main.transform.position); }
但し、unityのテキストメッシュなどでは仕様上、裏返って見えてしまう
その場合は
void Update () { transform.forward = cameraTransform.forward; }
のようにすると良い
これはカメラの位置を見ることをやめて、テキストメッシュにカメラのFocal Area(焦平面)への向きを定義している
普通のメッシュオブジェクトの場合は -cameraTransform.forward すればよい
transform のベクトルを forward, up, right の方向をセットする機能は強力であり、回転でなくベクトルをベースに考えることが出来て便利だが、注意すべきことがある。これらの値の一つをセットした場合、他のベクトルは制御出来ておらず、ときどきおかしな向きに反転したり回転していることがある
<要点>
- テキストメッシュは仕様上、普通にカメラ方向に向けると裏返って見えてしまう
- Vector3.forwardやupにはカメラと組み合わせると、いろいろな使い方が出来る
Quaternionクラスのメモ
回転成分に関連するクラス。Quaternionsは四元数という意味。クォータニオンは回転全体を基本的に四次元にてワンステップで実現する
これにより得られる回転成分を利用する事によりジンバルロック現象を回避できます
クォータニオンはUnityで回転を表現するために使用される内部形式(つまり変換するための行列とは別次元のもの)
クォータニオンのx、y、z、wパラメータは、インスペクタで表示されるオブジェクトの回転の値と関係がない。
x、y、z、wは角度で格納はされている(角度のサイン値、コサイン値で格納)
それらは複素数に基づいているので直観的に値を理解するのが難しくクォータニオンコンポーネント(x、y、z、w)に
ユーザーが直接アクセスする事は殆ど無いでしょう
既存のTransformに回転を加えたり、2つの回転を滑らかに繋げる処理を構築するために利用する事がありますが
それらは例外的であり、ほぼ99%利用されるのは以下の関数で、馴染み深いオイラー角をクォータニオンに変換、逆変換しながら利用する場面が多いと思います
Quaternion.LookRotation
Quaternion.Angle
Quaternion.Euler
Quaternion.Slerp
Quaternion.FromToRotation
Quaternion.identity
<資料>
<考察>
■オイラー角の特徴
- 3Dベクトルを使用するので理解しやすく視覚化も容易
- クォータニオンで扱うのは4変数。オイラーは3変数なので演算が若干速い
- 回転により得られる結果が必ずひととおりでない。ゲームエンジンによって回転の計算順が変わる。XYZの順番とは限らない(yxz等)
- ジンバルロックを起こす
■クォータニオンの特徴
- ジンバルロックの発生を回避できる
- 回転を複数の軸回転の組み合わせでなく、ターゲットベクトルにもとづいて処理できる
- 四元数、複素数に基づいて演算されるため内部計算が直観的でない
■何故、ジンバルロックが発生するか?
原因はxyz各回転値をひとつづつ個別に動かす必要があることに起因していて特定の組み合わせで
個々の次元が打ち消し合いターゲットの値に到達出来ないケースがある為
■クォータニオンを扱う上での注意点
掛ける順番に注意。マトリックスの時と同じように順番を変えると結果が変わるらしい。基本的にクォータニオンに対する操作は乗算にて行われる
複数のクォータニオン成分をTransform.rotationに反映させるには一回毎に分けて掛ける必要がある
<OKなやりかた>
tr.rotation = tr.rotation * Quaternion.AngleAxis(Time.deltaTime*20,Vector3.forward);
<NGなやりかた>
tr.rotation = Quaternion.AngleAxis(Time.deltaTime*20,Vector3.forward)*tr.rotation;
??????何故これが駄目になるのか原因がよく分からない。調査の必要アリ
<調べた結果の結論>
ローカル軸に対して相対的に計算される為このような事が起きる。基準になる値を先に書く必要がある
★Unityにより二つの表現方法を互いに変換させることが容易なので使用する都度に最適な方法を選択することが可能(オイラー角⇔クォータニオン)
.eulerAngles(オイラー角の取得) -> 角度を加工 -> Quaternion.Euler(加工した角度)
Quaternion.LookRotation関数
ベクトルの成分から、その方向のクォータニオンを返す。
重要なのは第二引数にアップベクターが指定できる事。これによりX軸の回転をロックできる
Quaternion.eulerAngles変数
回転のオイラー角を返す。
Vector3の値を返します。xyz各成分は直感的に理解しやすい値です
Quaternion.Euler関数
オイラー角を設定します。オブジェクトを回転させるときに使います。
Quaternionの値を返します。こちらは四元数なので、直感的に少し判りづらい
Quaternion.eulerAngles ⇔ Quaternion.Euler は相互変換できる
Quaternion.Slerp
2点間への回転を時間をかけて行う。
クォータニオンふたつ、floatひとつでクォータニオンを返す
Mathf.SmoothDampAngle関数
徐々に時間をかけて目標の角度へ向かう。
第三引数はrefで。引数はすべてfloat。返値もfloat
チュートリアルの学習
単純にターゲットの位置情報を利用して視線を向ける。この場合シーン内のメインカメラに向けている
transform.LookAt(Camera.main.transform.position);
このままだと敵と近接した際、クォータニオンのxz成分は無理に位置を合わせようして結果的に敵が仰向け気味の角度になってしまう
xz成分は邪魔なのでクォータニオンをオイラー角度に変換し角度を修正、その後、元のクォータニオンへと戻して敵の角度を変更する
Vector3 newRotation; newRotation = Quaternion.LookRotation(Camera.main.transform.position - transform.position).eulerAngles; newRotation.x=0; newRotation.z=0; transform.rotation = Quaternion.Euler(newRotation);
これによりYを軸とした角度のみが変更され、敵が仰向けになる事はなくなる
(Camera.main.transform.position - transform.position)はメインカメラと敵位置を引き算して向きのベクトルを導き出している
ゆっくりと回転AとBをつなげる処理
transform.rotation = Quaternion.Slerp(transform.rotation,Quaternion.Euler(newRotation),Time.deltaTime);
現在の向き~新しい回転角をなめらかに繋げて回転させる。lerp系の関数全般に言えることだが
本来、第三引数は0~1の値であり、この数値で進み具合を調節するが、この場合Time.deltaTimeは平均0.016666になり毎フレーム
少しづつ回転して行く(毎フレーム現値の1.6%づつ第二引数へ近づいていく)ので最終的に第二引数の値に到達する(終盤辺りは非常に小さな値になって行く)
このような、「第三引数がほぼ固定値」でも毎フレーム呼ばれれば最終的に到達するだろう的な考え方は重要
最大回転速度が適用された回転処理
public float maxRotSpeed = 40; //最大回転速度 public float miniTimeToReachTarget = 0.5f; //到達時間 Transform _tr, _camTr; float _velocity; //SmoothDampAngle関数が利用する値。現在の速度が格納される
void Start () { _tr = transform; _camTr = Camera.main.transform; } void Update () { Vector3 newRotation = Quaternion.LookRotation (_camTr.position - _tr.position).eulerAngles; Vector3 angle = _tr.rotation.eulerAngles; _tr.rotation = Quaternion.Euler ( angle.x, Mathf.SmoothDampAngle (angle.y, newRotation.y, ref _velocity, miniTimeToReachTarget, maxRotSpeed), angle.z); }
Transform _tr, _camTr;の意味。大事なのでそのまま引用
Transform を使用することを変数のアクセスと同様に捉えている人がいるかもしれないが、それは誤りだ。transform.position と記述したときは GetComponent<Transform>().position を短く記述していることと同様だ。繰り返しこの処理を行う場合はパフォーマンス上明らかなオーバーヘッドとなり、Transformおよびその他の同様の変数をキャッシュするのは良い習慣だ。
_trは参照として扱われるので値の変更も反映される
回転をスムーズに行う際 Quaternion.Slerp や Mathf.SmoothDampAngle など数種類の手段がある事が学習できた
引数の数は増えるが後者の方がより詳細な制御が可能な事がわかる
- 最終更新:2013-11-15 22:53:16