Matcapの歪み補正について

# 前書き
タグにUEとかつけていますが、内容はUEはあまり関係なくシェーダ/マテリアルの話です。検証環境としてUEを使ったのでタグ付けしました。

# 概要
軽量に高品質のライティングを再現するために、Matcapという手法が良く使われています。

http://tsubakit1.hateblo.jp/entry/2016/06/30/073000

詳しくはこのサイトを参照してもらった方が早いですが、ざっくり説明すると事前にレンダリングした画像を使用してライト表現を行う手法です。

カメラから見たライティング結果を画像に保存して、法線の方向に応じてその画像を参照する事で、疑似的なライティングが行えます。複雑なライティング計算をせずとも高品質なライトの結果（疑似的ではありますが）を得られるので、UnityやVRChat界隈でよく使われているようです。

今回は、使い慣れている環境の都合からUnrealEngine(UE-5.0.0)で検証しましたが、やっていることはエンジンに依存していないので、Unityなどどんな環境でも使えます。

また、記事内で使っているColorGridは

https://maniacbamboo.wordpress.com/2018/06/04/%E3%82%AB%E3%83%A9%E3%83%BC%E3%82%B0%E3%83%AA%E3%83%83%E3%83%89%E9%85%8D%E5%B8%83/

で配布されている物を使わせてもらいました。

# matcapの問題点
早速ですが、標準的なMatcapの計算では画面端にオブジェクトがあるとUVが歪む問題があります。

画面中央に置いた時は以下のように奇麗にテクスチャが貼られています。
![Default_Center.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/7a577759-ef59-cdb0-8aec-16460220b498.png)
しかし、対象のオブジェクトが画面端にあると以下のようにUVが歪み、思ったようなライティング結果が得られなくなります。
![Default.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/fd6f08f9-fa10-96ae-3c37-0f31371c5213.png)
この時のマテリアルはこんな状態です。
![M_Matcap-MaterialGraph_0_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/39f66672-e3e3-86a6-abb0-52e2b5373ca9.png)

https://twitter.com/kanihira/status/1061448868221480960

twitterでこの問題に対する修正方法を提案している人がいて、そこからヒントを得てさらに改善しました。

# Twitterで提案された手法を試してみる

UE標準の画角90度で提案されていた手法を試してみた結果がこちら
| 標準的な手法 | Twitterの手法 |
|:-:|:-:|
| ![Default.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/fd6f08f9-fa10-96ae-3c37-0f31371c5213.png) | ![CorrectTwitter.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/ab03caa8-8290-8156-bba2-f86d36518dcc.png) |

確かにいい感じに補正されています。しかし、Twitterの手法でも画角を広げてみると…
![CorrectTwitterWide.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/e5a15020-86bb-d8af-2fb5-015ac8649971.png)
このように歪みが出てきます。そこで、私の方で調整を加えてみたところ
![CorrectOwn.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/deb88de7-6d7c-95ab-99c6-fcb46309490f.png)
このような結果になりました。これでも歪みは残っているので解決策ではなく、回避策というレベルですが。

# そもそもの問題点
標準的なMatcapの手法に立ち返って、なぜUVの回り込みが発生するのかについて少し説明します。
<img alt="matcap_point.jpg" width="512" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/05bf9c50-302c-97c9-7c76-f14823744276.jpeg">
ここだけ手書き資料ベースになります（口頭説明したときの資料を再利用:sweat:）
まずUV展開は、左上図のようにビュー空間での法線方向によって求められています。
対象オブジェクトが正面にある時は良いのですが、右上図のように画面端に行くと **見える範囲** と **UV展開している範囲** がずれてしまい、UVが回り込んでいる先が見えてしまします。

それを補正するために、画面端ほど傾く視線方向ベクトルを使って補正してあげましょう。というのが今回の手法です。画角が広い場合、画面端の視線方向ベクトルの傾きは大きくなります。そのため、画角が広いと補正が効きすぎて歪みが目立つ結果になっていました。

# 今回の改善手法
まず、Twitterの手法で使われている、ShaderForgeのNormalBlendの式はおそらく

https://blog.selfshadow.com/publications/blending-in-detail/

の最後の方にある blend_rnm()の関数です。

また、上記のサイトに書かれている式は、法線マップのテクスチャに格納されている状態から、同じくテクスチャに格納されている状態に戻すところまでやっていて、シェーダ内で行うには無駄があります。
それらを省いた関数が以下の関数です。

```hlsl:Reoriented Norml Map
half3 blend_rnm(half3 n1, half3 n2)
{
  half3 t = n1 * half3(-1, -1, 1) + half3(0, 0, 1);
  half3 u = n2 * half3(-1, -1, 1);
  half3 r = normalize(t*dot(t, u) - u*t.z);
  return r;
}
```
このうち補間係数となり得るのが、関数内1行目の `+ half3(0, 0, 1);` の部分で、これを **1.25** に変更したのが私が行った改善策です。

切り替え機能などを盛り込んで、最終的なマテリアルは以下のような形になりました。
![M_Matcap-MaterialGraph_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431039/16e519c5-8609-b33a-6b5b-8be76e23b0e6.png)

# まとめ

ビュー空間のXYでUVを作っているために起きている回りこみの問題を、視線方向を使って補正する。というのがこの対応の肝です。
その為、視線方向以外に何かいい補正基準があればそれを使ってもいいですし、ブレンド方法もRNMでなく他の方法を用いても良いかもしれません。

何か他に良い補正方法を思いついた人がいたら、教えてください！