e.blog

主にUnity/UE周りのことについてまとめていきます

TransformクラスのTransform**** / InverseTransform****系メソッドのメモ

以前、Qiitaで書いていた記事の移植です。
Inverse系も追加したのでこちらに移動しました。

概要

TransformPointTransformVectorTransformDirectionがそれぞれなにをやってくれているのか、ピンと来てなかったので、どういう処理をしたら同じ結果が得られるのか書いてみたのでメモ。

まぁやってることはシンプルに、モデル行列掛けているだけだけど、その後の処理で平行移動を無効化したり正規化したり、といったことをしている。方向だけを取りたい、みたいなときに使う。
なにやってるか分からないと気持ち悪かったので書いてみた、完全にメモですw

Transform****系メソッド

// ----------------------------------------------------------------------------
// TransformPoint
// 意味: 通常のモデル行列を掛けたポイントを得る
Vector3 point = Vector3.one;
Vector3 newPoint1 = transform.TransformPoint(point);

Vector4 v = new Vector4(point.x, point.y, point.z, 1f);
Vector3 newPoint2 = transform.localToWorldMatrix * v;

// ↓Vector4作らないで済むのでこちらのほうが便利
// Vector3 newPoint2 = transform.localToWorldMatrix.MultiplyPoint(point);

// ----------------------------------------------------------------------------
// TransformVector
// 意味: 回転を適用したのちに、平行移動を無効化したベクトルを得る
Vector3 vector = new Vector3(0, 1.5f, 0);
Vector3 newVector1 = transform.TransformVector(vector);

Vector4 v = new Vector4(vector.x, vector.y, vector.z, 1f);
Vector3 newVector2 = (transform.localToWorldMatrix * v) - transform.position;

// ↓Vector4作らないで済むのでこちらのほうが便利
// Vector3 newVector2 = transform.localToWorldMatrix.MultiplyVector(vector);

// ----------------------------------------------------------------------------
// TransformDirection
// 意味: 回転を適用したのちに、平行移動を無効化し正規化したベクトルを得る
Vector3 direction = Vector3.up;
Vector3 newDirection1 = transform.TransformDirection(direction);

Vector4 v = new Vector4(direction.x, direction.y, direction.z, 1f);
Vector3 newDirection2 = ((transform.localToWorldMatrix * v) - transform.position).normalized;

// ↓Vector4作らないで済むのでこちらのほうが便利
// Vector3 newDirection2 = transform.localToWorldMatrix.MultiplyVector(direction);

InverseTransform****系メソッド

// ----------------------------------------------------------------------------
// InverseTransformPoint
// 意味: ワールド座標からローカル座標へ変換する

Vector3 newPoint1 = transform.InverseTransformPoint(_v);

Matrix4x4 m = transform.worldToLocalMatrix;
Vector4 p = new Vector4(_v.x, _v.y, _v.z, 1f);
Vector3 newPoint2 = m * p;


// ----------------------------------------------------------------------------
// InverseTransformVector
// 意味: 指定したベクトルの方向のみ、ワールド座標からローカル座標へ変換する

Vector3 newdir1 = transform.InverseTransformVector(_v);

Matrix4x4 m = transform.worldToLocalMatrix;
Vector4 c0 = m.GetColumn(0);
Vector4 c1 = m.GetColumn(1);
Vector4 c2 = m.GetColumn(2);
Vector4 c3 = new Vector4(0, 0, 0, 1f);
Matrix4x4 newM = new Matrix4x4(c0, c1, c2, c3);
newM = newM.inverse.transpose;

Vector4 p = new Vector4(_v.x, _v.y, _v.z, 1f);
Vector3 newdir2 = newM * p;

// ↓内容的には以下でまかなえる
// Vector3 newdir2 = transform.worldToLocalMatrix.MultiplyVector(_v);


// ----------------------------------------------------------------------------
// InverseTransformDirection
//
// ※ InverseTransformVectorの正規化した単位ベクトルを得るのかと思いきや、InverseTransformVectorと同じだったので割愛

解説

Inverse系のVector処理はやや複雑です。抜粋するとコードは以下。

Matrix4x4 m = transform.worldToLocalMatrix;
Vector4 c0 = m.GetColumn(0);
Vector4 c1 = m.GetColumn(1);
Vector4 c2 = m.GetColumn(2);
Vector4 c3 = new Vector4(0, 0, 0, 1f);
Matrix4x4 newM = new Matrix4x4(c0, c1, c2, c3);
newM = newM.inverse.transpose;

取り出した、ワールドからローカルへの変換行列をさらに分解して新しい行列を生成しています。
ここでやっていることは方向のみのベクトルを扱う関係で平行移動部分を除去し、移動なしの行列に変換しています。

そして最後に、逆転置行列にするためにnewM = newM.inverse.transpose;としています。

なぜ逆転置行列なのか。
それには理由があります。詳細については以下の記事がとても詳しいのでそちらを御覧ください。

raytracing.hatenablog.com

言葉で説明している箇所を引用させていただくと、

頂点について、モデル座標系からワールド座標系に移すときには何らかの変換行列を使って変換する。
しかし、頂点に対して作用する行列と同じものを法線に対して作用させてもうまくいかない。 まず、平行移動成分を除去しなければならない。法線は向きのみの量なので平行移動は関係ないからである。
回転移動成分についてはそのまま使うことが出来る。
拡大縮小などのスケーリング成分については、たとえば各頂点をX軸方向に3倍にする場合、法線はX軸方向に1/3倍にする必要がある。(最終的に正規化して長さは1にする)

これらの各要素を考慮しつつ法線を適切に変換するための行列を求めるには、頂点の変換行列の逆転置行列を求めればよい、ということが知られている。

ということ。
実際にベクトルを書いてみると分かりますが、スケールを掛けると方向が異なった方向を向いてしまいます。
しかし今回の求めるべきベクトルは方向を適切に変換することにあります。

なので上記のように「逆転置行列」を使う必要がある、というわけです。