初めに
WebGLなど3DCGを扱うとき、法線に対して座標変換行列の逆転置行列(逆行列にした後、転置行列にする行列)をかけることで法線を正しい向きに修正することがありますが、その内容をかみ砕いた記事です。
なぜ法線の修正が必要か
物体の頂点を移動させるモデル座標変換行列では回転や拡大縮小のスケール変化や平行移動があります。
物体の頂点が移動するとき、法線も正しい向きに修正しなければ、一定の向きのままではライティングなどの処理では陰影が変化せず、不自然なレンダリング結果になってしまうため、頂点の変化に合わせて法線も正しく修正させてあげる必要があります。
法線の修正方法
法線を正しい向きに修正するには、物体に動きが生じたときに法線がどのような影響を受けるかを知ることで適切に処理をすることができます。
- 回転に関しては、物体が回転すると法線も同じように回転するはずです。そのため、同じ回転成分を法線に適用させることで法線を正しい向きにすることができます
- スケール変化に関しては、物体が $S$ 倍に変化したとき法線は $1 / S$ 倍することで正しい向きにすることができます
- 平行移動に関しては、法線の向きに影響はないため、特に考慮する必要はありません。
平行移動
平行移動の成分では各軸方向に$T_x$, $T_y$, $T_z$だけ移動するとき、次のように表すことができます。
T = \left( \begin{array}{ccc}
1 & 0 & 0 & T_x \\
0 & 1 & 0 & T_y \\
0 & 0 & 1 & T_z \\
0 & 0 & 0 & 1
\end{array} \right)
つまり、平行移動成分を無視するということはモデル座標変換行列の4×4の成分のうち左上の3×3の成分だけ抜き出した行列$M$について考えればいいということです。
修正するための行列
行列$M$は 2 つの回転行列$R_1$, $R_2$と 1 つの拡大縮小(スケール)行列$S$に分解することができます(特異値分解)。
M = R_1 * S * R_2
法線を正しい向きに修正するためには、回転成分$R$をそのまま法線にも適用させ、スケール変化の成分では$S$の逆数をかける必要がありました。
スケール変化の逆数は、ベクトルではそのベクトルの逆行列を作用させることを意味します。(かけ合わせると 1(単位ベクトル)になる)
今回求めたい法線を正しい向きにするための行列
を$M_w$とすると、回転成分をそのまま適用させ、スケール成分の逆行列にすると次のように表せます。
M_w = R_1 * S^{-1} * R_2
求めたい行列$M_w$が定義できたので、ここからは色々変換しながら、なぜ逆転置行列になるのかをたどっていきます。
スケール
スケール変化の成分に関しては各軸方向に$S_x$倍, $S_y$倍, $S_z$倍するとき、次のように表すことができます。
S_{xyz} = \left( \begin{array}{ccc}
S_x & 0 & 0 & 0 \\
0 & S_y & 0 & 0 \\
0 & 0 & S_z & 0 \\
0 & 0 & 0 & 1 \\
\end{array} \right)
この行列から、スケール変化の成分を示す行列$S$は転置行列にしても同じ結果になることがわかります。
転置行列とは次のように行と列を入れ替えた行列のことです。
A = \left( \begin{array}{ccc}
a_{11} & a_{12} & a_{13} \\
a_{21} & a_{22} & a_{23} \\
a_{31} & a_{32} & a_{33} \\
\end{array} \right)
A^{T} = \left( \begin{array}{ccc}
a_{11} & a_{21} & a_{31} \\
a_{12} & a_{22} & a_{32} \\
a_{13} & a_{23} & a_{33} \\
\end{array} \right)
従って、行列$M_w$は、$S^{-1} = (S^{-1})^{T}$であることを利用して次ように式を変化させることができます。
M_w = R_1 * (S^{-1})^{T} * R_2
回転
3 次元の各軸周りの回転行列は次のように表すことができます。
R(\theta _x) = \left( \begin{array}{ccc}
1 & 0 & 0 & 0 \\
0 & \cos \theta _x & -\sin \theta _x & 0 \\
0 & \sin \theta _x & \cos \theta _x & 0 \\
0 & 0 & 0 & 1 \\
\end{array} \right)
R(\theta _y) = \left( \begin{array}{ccc}
\cos \theta _y & 0 & \sin \theta _y & 0 \\
0 & 1 & 0 & 0 \\
-\sin \theta _y & 0 & \cos \theta _y & 0 \\
0 & 0 & 0 & 1 \\
\end{array} \right)
R(\theta _z) = \left( \begin{array}{ccc}
\cos \theta _z & -\sin \theta _z & 0 & 0 \\
\sin \theta _z & \cos \theta _z & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
\end{array} \right)
これらはそれぞれ転置行列と逆行列が等しくなる直行行列と言われる行列です。
直行行列の性質から次の等式が成り立ちます。
R(\theta)^{T} = R(\theta)^{-1}
この等式が成り立つとき、「逆行列の転置行列」は「転置行列の転置行列」と表すことができ、「転置行列の転置行列」は元の行列の行と列を 2 回入れ替えるということなので元の行列と同じになります。
(R^{-1})^{T} = (R^{T})^{T} = R
$R = (R^{-1})^{T}$ となる性質を利用して次のように変換できます。
M_w = (R_1^{-1})^{T} * (S^{-1})^{T} * (R_2^{-1})^{T}
最後に
転置行列は掛ける順を逆にすることでまとめることができ、逆行列もまた掛ける順を逆にすることでまとめることができます。
M_w = (R_2^{-1} * S^{-1} * R_1^{-1})^{T} = ((R_1 * S * R_2)^{-1})^{T}
もともとの行列は$M = R_1 * S * R_2$であることから、法線を正しい向きにする行列$M_w$は逆行列の転置行列で表すことができるというわけです。
M_w = (M^{-1})^{T}
ref.
https://paroj.github.io/gltut/Illumination/Tut09%20Normal%20Transformation.html
https://imagingsolution.net/math/rotation-scaling-translation-3d-matrix/
https://raytracing.hatenablog.com/entry/20130325/1364229762