[UE4] マテリアルに３軸方向からテクスチャを貼ってUVの境目を目立たなくする

# 追記

ワールド座標に合わせてテクスチャを貼るには `UV_WorldScalable` を利用するのが簡単で負荷も低くおススメ。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1083/229343fc-c231-b949-8eff-f93bc2beb117.png)

本記事の内容は、テクスチャフェッチ回数が3倍になる。
テクスチャフェッチは高価な処理なのでこの実装はおススメできない。

# 元記事

適当に背景素材を作っていると、テクスチャの入り方がおかしくなる場合が多々あります。

例えば、 Sphere 形状のメッシュにテクスチャを貼ると、次の様に UV の境目が目立ちます。
![normal_tilling.png](https://qiita-image-store.s3.amazonaws.com/0/1083/c4126b7b-818d-7375-9601-478264fbb783.png)
また、UV(0, 0), UV(1, 1) の点へテクスチャが吸い込まれてるような感じになり、半径が大きな中央部分のテクスチャも伸びいます。
（繋ぎ目が目立つように半端な値でタイリングしています）

この原因は UV の座標値が次のようになっている為です。
![uv_color.png](https://qiita-image-store.s3.amazonaws.com/0/1083/28ea3d62-6d33-765d-cdb1-3b64d67cdd38.png)

<details><summary>テクスチャの UV 座標</summary><div>
![uv_coord.png](https://qiita-image-store.s3.amazonaws.com/0/1083/7bd33f3b-2c7b-37a1-59df-9f4c1b2f3183.png)
[waifu2x](http://waifu2x.udp.jp) で 2^2 倍に拡大した。
</div></details>

今回は、どんな形状でもそれとなくいい感じにテクスチャが入るマテリアルを作成していきます。

## 3軸方向からテクスチャを貼るマテリアルを作成する

複数の方向からテクスチャを貼ることで上記の課題の解決を試みます。
今回利用するテクスチャはスターターコンテンツに含まれる次のアセットです。

![textures.PNG](https://qiita-image-store.s3.amazonaws.com/0/1083/3e31a2b3-974f-d6b0-3720-994e6b7d45fa.png)

まずは、3軸方向からテクスチャを貼るための情報を生成します。
各 XYZ 軸の両面から、直交する座標を UV としてテクスチャを貼っていきます。
X 軸方向に対しては YZ を UV とする感じです。
また、各軸に対するテクスチャを貼る部分と UV の境目をブレンドするマスク値を生成します。

![uv_and_mask.PNG](https://qiita-image-store.s3.amazonaws.com/0/1083/58acfc28-09a5-9074-737b-7dedff56fa84.png)
### 3軸方向の UV の生成
UV の値はピクセル座標の xyz から、２チャンネルずつ組合せて、３軸方向の UV 値を生成します。

1. ピクセル座標を ***WorldPosition*** から取得し、***ObjectPosition*** を引くことで、オブジェクトの移動に対応します。
2. ピクセル座標を ***TransformVector*** で WorldSpace から LocalSpace へ変更し、オブジェクトの回転へ対応します。
3. LocalSpace へ変更すると長さが正規化されるので、***ObjectScale*** を掛けてオブジェクトの拡縮へ対応しています。

- Mask(R, B): xz 座標を UV 座標とする
- Mask(R, G): xy 座標を UV 座標とする
- Mask(G, B): yz 座標を UV 座標とする

### 3軸方向のマスク値の生成
マスクの値は、頂点の法線の向きを基準に生成します。

1. 法線情報を ***VertexNormalWS*** から取得し、LocalSpace へ変更することでオブジェクトの回転へ対応します。
2. 法線の値は、*`-1 ~ 1`* の値を取るので、***Abs*** で絶対値へ変換し、各軸の両面へマスクが掛かるようにします。
3. ***Power*** マスク境界のブレンドを調整できるようにします。
4. 各チャンネルの値を平準化し、Vector を単一チャンネルへ分解します。

マスク値を可視化すると次のようなサイコロ状になり、3軸6方向にマスクが掛かるのが分かります。
![mask_color.PNG](https://qiita-image-store.s3.amazonaws.com/0/1083/76bc62ac-ac1c-48e0-d2a7-d97490c4a3b9.png)

- R: X 座標に対するマスク値（直交する YZ 座標の UV に対するマスク）
- G: Y 座標に対するマスク値（直交する XZ 座標の UV に対するマスク）
- B: Z 座標に対するマスク値（直交する XY 座標の UV に対するマスク）

## テクスチャをサンプリングしてマスクしてブレンド

それぞれの UV でサンプリングした値を ***Multiply*** でマスクし、***Add*** でブレンドします。
各UVに対し、直交する軸のマスク値を利用します。
***`UV(R, B) * Mask(G)`*** + ***`UV(R, G) * Mask(B)`*** + ***`UV(G, B) * Mask(R)`***

<details><summary>ベースカラーグラフ</summary><div>
（アルファからラフネスを取っています）
![triplar_basecolor.PNG](https://qiita-image-store.s3.amazonaws.com/0/1083/f88a1329-7742-af6a-28b0-64b12715e601.png)
</div></details>

<details><summary>ノーマルマップグラフ</summary><div>
![triplar_normal.PNG](https://qiita-image-store.s3.amazonaws.com/0/1083/85b195b8-5341-ffca-efba-491326a3dd4f.png)
</div></details>

## レンダリング結果を確認する

こんな感じで、テクスチャを均一に貼ることができるました。
ブレンドしているところはよく見れば分かりますが、オブジェクトを回転して目立たないようにするなりしましょう。
![triplaner_texture.PNG](https://qiita-image-store.s3.amazonaws.com/0/1083/03e22005-1ab4-dfcf-538a-95e75c088095.png)

## サンプル数が多くて重くない？

各ピクセルで6回のサンプリングを行うので、場合によっては問題になる可能性があります。

マスク軸のどれかが１に近ければ、マスクもブレンドも行う必要はなく、最も大きいマスク値に直交する UV 座標で１回サンプリングを行うだけで良いかもしれません。

これは ***CustomNode*** でシェーダーコードを直接書くことで実装できます。

```glsl:ExpressionCode
/** 引数定義
 * Tex: TextureObject
 * UV3: float3 (UV座標に使う vector, UV の VertexInterpolator からとってくる)
 * Mask3: float3 (マスク座標に使う vector, Mask の VertexInterpolator からとってくる)
 * Threshold: float (しきい値, 0.999 とか)
 * OutputType: float4
 */

float4 ret;

[branch] if (Mask3.x >= Threshold) {
  ret = Texture2DSample(Tex, TexSampler, float2(UV3.y, UV3.z));
}
else [branch] if (Mask3.y >= Threshold) {
  ret = Texture2DSample(Tex, TexSampler, float2(UV3.x, UV3.z));
}
else [branch] if (Mask3.z >= Threshold) {
  ret = Texture2DSample(Tex, TexSampler, float2(UV3.x, UV3.y));
}
else {
  float4 sample_x = Texture2DSample(Tex, TexSampler, float2(UV3.y, UV3.z));
  float4 sample_y = Texture2DSample(Tex, TexSampler, float2(UV3.x, UV3.z));
  float4 sample_z = Texture2DSample(Tex, TexSampler, float2(UV3.z, UV3.y));
  ret = lerp(lerp(sample_x, sample_y, Mask3.y), sample_z, Mask3.z);
}

return ret;  // BaseColor のリターン

// return UnpackNormalMap(ret);  // NormalMap は UnpackNormalMap() して返す
```

こんな感じでサンプリング数を減らして軽量化できるかもしれません。

***CustomeNode*** は Output を追加できないので、結局、BaseColor と NormalMap で最小でも 2回のサンプリングが行われてしまう。