法線を正しい向きにするときなぜ逆転置行列なのか

# 初めに

WebGLなど3DCGを扱うとき、法線に対して座標変換行列の逆転置行列(逆行列にした後、転置行列にする行列)をかけることで法線を正しい向きに修正することがありますが、その内容をかみ砕いた記事です。

# なぜ法線の修正が必要か

物体の頂点を移動させるモデル座標変換行列では回転や拡大縮小のスケール変化や平行移動があります。

物体の頂点が移動するとき、法線も正しい向きに修正しなければ、一定の向きのままではライティングなどの処理では陰影が変化せず、不自然なレンダリング結果になってしまうため、頂点の変化に合わせて法線も正しく修正させてあげる必要があります。

# 法線の修正方法

法線を正しい向きに修正するには、物体に動きが生じたときに法線がどのような影響を受けるかを知ることで適切に処理をすることができます。

- 回転に関しては、物体が回転すると法線も同じように回転するはずです。そのため、同じ回転成分を法線に適用させることで法線を正しい向きにすることができます
- スケール変化に関しては、物体が $S$ 倍に変化したとき法線は $1 / S$ 倍することで正しい向きにすることができます
- 平行移動に関しては、法線の向きに影響はないため、特に考慮する必要はありません。

# 平行移動

平行移動の成分では各軸方向に$T_x$, $T_y$, $T_z$だけ移動するとき、次のように表すことができます。

```math
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$に分解することができます(特異値分解)。

```math
M = R_1 * S * R_2
```

法線を正しい向きに修正するためには、回転成分$R$をそのまま法線にも適用させ、スケール変化の成分では$S$の逆数をかける必要がありました。
スケール変化の逆数は、ベクトルではそのベクトルの逆行列を作用させることを意味します。(かけ合わせると 1(単位ベクトル)になる)

今回求めたい`法線を正しい向きにするための行列`を$M_w$とすると、回転成分をそのまま適用させ、スケール成分の逆行列にすると次のように表せます。

```math
M_w = R_1 * S^{-1} * R_2
```

求めたい行列$M_w$が定義できたので、ここからは色々変換しながら、なぜ逆転置行列になるのかをたどっていきます。


# スケール

スケール変化の成分に関しては各軸方向に$S_x$倍, $S_y$倍, $S_z$倍するとき、次のように表すことができます。

```math
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$は転置行列にしても同じ結果になることがわかります。

転置行列とは次のように行と列を入れ替えた行列のことです。

```math
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)
```

```math
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}$であることを利用して次ように式を変化させることができます。

```math
M_w = R_1 * (S^{-1})^{T} * R_2
```

# 回転

3 次元の各軸周りの[回転行列](https://ja.wikipedia.org/wiki/%E5%9B%9E%E8%BB%A2%E8%A1%8C%E5%88%97)は次のように表すことができます。

```math
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)
```

```math
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)
```

```math
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)
```

これらはそれぞれ転置行列と逆行列が等しくなる[直行行列](https://ja.wikipedia.org/wiki/%E7%9B%B4%E4%BA%A4%E8%A1%8C%E5%88%97)と言われる行列です。

直行行列の性質から次の等式が成り立ちます。

```math
R(\theta)^{T} = R(\theta)^{-1}
```

この等式が成り立つとき、「逆行列の転置行列」は「転置行列の転置行列」と表すことができ、「転置行列の転置行列」は元の行列の行と列を 2 回入れ替えるということなので元の行列と同じになります。

```math
(R^{-1})^{T} = (R^{T})^{T} = R
```

$R = (R^{-1})^{T}$ となる性質を利用して次のように変換できます。

```math
M_w = (R_1^{-1})^{T} * (S^{-1})^{T} * (R_2^{-1})^{T}
```

# 最後に

転置行列は掛ける順を逆にすることでまとめることができ、逆行列もまた掛ける順を逆にすることでまとめることができます。

```math
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$は逆行列の転置行列で表すことができるというわけです。

```math
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