ことわり
この記事では行列はRow Major(行優先で話を進めていきます)
View変換とは
ワールド座標系に置かれたオブジェクト群をカメラを原点とした座標系に変換する行列を指します.現実世界でいうと写真を撮っていると時に自分を原点として被写体がどの位置にいるかってことです.
結論から言うと以下の行列です.
\begin{pmatrix}
right_x & up_x & forward_x & 0 \\
right_y & up_y & forward_y & 0 \\
right_z & up_z & forward_z & 0 \\
v_x & v_y & v_z & 1
\end{pmatrix}
(*ちょっと名前が長いので以下からこういう対応になります : right->rx, up -> ry, forward -> rz)
View行列を出すためのステップ
- Camera行列を構成する平行移動行列と回転行列を求める
- 1で求めたそれぞれの逆行列を求める
- それぞれの逆行列をかけてView行列を算出
だいたいこんな感じです.
Camera行列を構成する平行移動行列と回転行列を求める
View行列を求めたいのに何で違う行列求めないといけないのかってことを初めに言います.
Camera(視点)は基本的に座標,注目点,上向きベクトルで構成されます.またcamera行列は回転行列x平行移動行列で表現されます.この行列を作用させることでCameraの位置に行けるってことです.でも今ってその逆でcameraがある場所を原点としたいのでcamera行列の逆行列がView行列になるんですねぇ.それではCamera行列求めていきましょう.
(以下:Eye(eye.x, eye.y,eye.z,Target(target.x, target.y, target.z), Up(Up.x, Up.y, Up.z)と書きます)
回転座標成分
回転成分はCameraの基底(Cameraが持っている座標軸成分)がそのまま回転行列となります.
Z軸成分
rz = (Target - Eye) / |(Target - Eye)|
x軸成分
rx = (Up \times \ Target) / |(Up \times \ Target)|
y軸成分
ry = z \times \ x
「なんでyもう一回出してるのって?上向きベクトルって初めに設定してるじゃん」って思うかもしれないんですが,実際x, z軸に対して垂直って保証はないのでもう一回計算してあげてます.
回転行列
後はこれらをRowMajorに従って並べるだけ.
\begin{pmatrix}
rx_x & rx_y & rx_z & 0 \\
ry_X & ry_y & ry_z & 0 \\
rz_x & rz_y & rz_z & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
平行移動成分
Cameraの座標がT(t.x, t.y, t.z)の時,以下の行列で表現できます.ここの時点で怪しい人はアフィン変換とか同次座標系とかってgoogleセンセに聞いてみてください.
\begin{pmatrix}
0 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 \\
0 & 0 & 0 & 0 \\
t_x & t_y & t_z & 1 \\
\end{pmatrix}
Cameta行列の平行移動,回転行列の逆行列を求める
回転行列
それぞれの成分の大きさが1かつ直交している行列のことを正規直交基底って言います.これの何がうれしいかって逆行列が転置するだけで求められるんですよ.最高です
\begin{pmatrix}
rx_x & ry_x & rz_x & 0 \\
rx_y & ry_y & rz_y & 0 \\
rx_z & ry_z & rz_z & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
平行移動成分
逆方向に移動させるだけです.
\begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-t_x & -t_y & -t_z & 1 \\
\end{pmatrix}
それぞれの逆行列をかけてView行列を算出
ここで掛け算の順番が変わってます.それはそれぞれの行列が正則行列である時,行列の積の逆行列は元々の掛け算の順を逆にして逆行列にしたものと等しくなります.証明は簡単なので見てみるといいかも.
M_v = \begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-t_x & -t_y & -t_z & 1 \\
\end{pmatrix}
\begin{pmatrix}
rx_x & ry_x & rz_x & 0 \\
rx_y & ry_y & rz_y & 0 \\
rx_z & ry_z & rz_z & 0 \\
0 & 0 & 0 & 1
\end{pmatrix} =
\begin{pmatrix}
rx_x & ry_x & rz_x & 0 \\
rx_y & ry_y & rz_y & 0 \\
rx_z & ry_z & rz_z & 0 \\
v_x & v_y & v_z & 1
\end{pmatrix}
(vについては次項)
内積
上記で算出したview行列ですが,v成分はview行列の1~3行目の回転成分と平行移動行列逆行列の内積表現となっています.実際手で上の式を解いてみるとわかるかと思います.
サンプルソース
glm::mat4 MatrixUtils::lookAt(glm::vec3 e, glm::vec3 u, glm::vec3 t) {
glm::mat4 dest;
glm::vec3 eye = e;
glm::vec3 up = u;
glm::vec3 target = t;
float x0, x1, x2, y0, y1, y2, z0, z1, z2;
z0 = eye[0] - target[0];
z1 = eye[1] - target[1];
z2 = eye[2] - target[2];
float l = 1.0 / sqrtf(pow(z0, 2) + pow(z1, 2) + pow(z2, 2));
z0 *= l;
z1 *= l;
z2 *= l;
x0 = up[1] * z2 - up[2] * z1;
x1 = up[2] * z0 - up[0] * z2;
x2 = up[0] * z1 - up[1] * z0;
l = 1.0 / sqrtf(pow(x0, 2) + pow(x1, 2) + pow(x2, 2));
x0 *= l;
x1 *= l;
x2 *= l;
y0 = z1 * x2 - z2 * x1;
y1 = z2 * x0 - z0 * x2;
y2 = z0 * x1 - z1 * x0;
l = 1.0 / sqrtf(pow(y0, 2) + pow(y1, 2) + pow(y2, 2));
y0 *= l;
y1 *= l;
y2 *= l;
glm::vec4 x = glm::vec4(x0, y0, z0, 0);
glm::vec4 y = glm::vec4(x1, y1, z1, 0);
glm::vec4 z = glm::vec4(x2, y2, z2, 0);
glm::vec4 w = glm::vec4(
-(x0 * eye[0] + x1 * eye[1] + x2 * eye[2]),
-(y0 * eye[0] + y1 * eye[1] + y2 * eye[2]),
-(z0 * eye[0] + z1 * eye[1] + z2 * eye[2]),
1.0);
dest = glm::mat4(x, y, z, w);
return dest;
}