接空間を用いたInteriorMapを幾何的に理解する(Unity)

# はじめに
接空間を用いたInteriorMapの実装を理解するための備忘録です。細かい部分は大雑把に省き、要点のみまとめていきます。

# 必要な知識
・Shaderの基礎知識
・Shader Graph(Unity)
・線形代数(ベクトル,座標変換,接空間)

# シェーダーの概要
G2 Studios様の記事に記載されていたシェーダーを元に解説します。
[インテリアマッピングの作成検証をしてみました](https://note.g2-studios.net/n/n4d8035653aa5?gs=d386fc1f607e)

インテリアマップは格子状の空間(ビルの内装など)を疑似的に描画するための手法です。視点を変えると内部にキューブ上の空間が展開されたように見えます。
![](https://storage.googleapis.com/zenn-user-upload/94e38b4f8000-20230221.png)
 説明をする際に、疑似的に見える平面やメッシュの平面を混同してしまう恐れがあるため、 便宜上、メッシュを**メッシュ平面**、内部に展開された平面を**インテリア平面**、インテリマップの内部の空間座標を**インテリア空間**と呼ぶことにします。

# キューブマップのサンプル方向
![](https://storage.googleapis.com/zenn-user-upload/ccdc15acdaf2-20230221.png)
 このシェーダーの要点はキューブマップをサンプリングする位置の計算方法です。(色域がクランプされてボロノイ図のような見た目になっていますが、実際は綺麗なグラデーションになっています)

https://www.youtube.com/watch?v=u6S0e7RJ0Jg
 平面のメッシュに適応した際の図です。視点が変わると内部のサンプリング位置も立体的に変わります。

![](https://storage.googleapis.com/zenn-user-upload/c6a7e085735d-20230221.png)
　幾何的な意味としては、**インテリア空間の原点O から インテリア平面の各点 に対して照射したベクトル**を意味します。

![](https://storage.googleapis.com/zenn-user-upload/a72585a02341-20230221.png)
これを求めるには、接空間の視線ベクトル$\vec{V}$を用いて

点A　:　視点から$\vec{V}$方向に照射したレイ　と　メッシュ平面　の交点 
点B　:　点Aから$\vec{V}$方向に照射したレイ　　と　インテリア平面　の交点

としたときに

$$\vec{OB} = \vec{OA} + \vec{AB}$$

で求めることができます。

## OAの計算

![](https://storage.googleapis.com/zenn-user-upload/be4c2fb91bdd-20230221.png)
$\vec{OA}$はUV座標と任意のスカラー値で求めることができます。
詳細は省きますが、タイリング計算を行ったり、値域を$[0,1]$から$[-1,1]$に変換しています。
wは任意のスカラー値(負数)です。(インテリア空間の原点から奥方向が正なのでメッシュ平面のw座標は負数になります)

## ABの計算
![](https://storage.googleapis.com/zenn-user-upload/c070f76ae45d-20230221.png)
 $\vec{AB}$は　$\vec{V}$とごちゃごちゃとしたノードの値の積
 によって求められています。 
 ごちゃごちゃとしたノードの値を描画したものが以下の画像です。
 ![](https://storage.googleapis.com/zenn-user-upload/dd9cccd20d95-20230221.png)
 　文字ベースで説明するのは難しいので二次元平面(UW平面)で説明します。
 ![](https://storage.googleapis.com/zenn-user-upload/c8506ce92e65-20230221.png)
 メッシュを上側から覗いた状態だと考えてください。点線がインテリア平面を表しています。
求めたいのはベクトル$\vec{AB}$です。
![](https://storage.googleapis.com/zenn-user-upload/dfb8b1f09443-20230221.png)
青い矢印が$\vec{V}$を表しています。このベクトルは常に大きさが1です。この値をそのまま使ってしまうと平面を突き破った先の座標を求めてしまいます。
この$\vec{V}$をいい感じに縮尺する比率を、ごちゃごちゃとしたノードで計算しています。
正確に表現すると、**点$A$から$\vec{V}$方向に照射したレイ** と **u = 1 , v = 1 , w = 1に展開したインテリア平面**　までの　**距離$(|x|,|y|,|z|)$のうち最小のもの**を表しています。

# インテリア平面までの距離計算
![](https://storage.googleapis.com/zenn-user-upload/61f707971db9-20230221.png)
ShaderGraphのノードは上図のようになっています。左半分を計算式に直すと以下の通りです。

$$1/\vec{|V|} - \vec{OA}/\vec{V}$$

ここで、一般的にShader言語におけるベクトルに対するベクトルの割り算は各要素に対して行う処理を意味するので、右方の計算は$\vec{OA}/\vec{V} = (\vec{OA}_u/\vec{V}_u,\vec{OA}_v/\vec{V}_v,\vec{OA}_w/\vec{V}_w)$を表しています。

数式やノードを見てもイマイチピンとこないのでUW座標系で説明します。
![](https://storage.googleapis.com/zenn-user-upload/d3da452da6d4-20230221.png)
求めたい値$|x|$は三角形の比から考えられます。
![](https://storage.googleapis.com/zenn-user-upload/e44842708042-20230221.png)
u軸上に配置されたインテリア平面は原点から距離$u = 1$だけ離れた位置にあるため三角形の比は
$$|x| : 1 = 1 - |OA_u| : |V_u|$$
です。上式を変形すると
$$|x| = (1 - |OA_u|) / |V_u|$$
ここで$|OA_u|$と$|V_u|$の符号は常に等しいため絶対値を取り除きます。
$$|x| = 1/\vec{|V_u|} - \vec{OA_u}/\vec{V_u}$$

これでU座標に配置されたインテリア平面との距離を求められました。
これをVW座標に対しても行い、最小の値を選ぶことでUVWに展開されたインテリア平面までの距離を求めることができます。

# 終わりに
ノードから逆算して考えたため余計ややこしい部分はありますが、要素に分解して考えることで直感的にシェーダーをいじることができるようになりそうです。