VR空間での作品展示：タイリングとshaderで容量を節約

VRやメタバースには様々なイベントが生まれてきています
おそらくVRで一番メジャーなVket6というイベントに僕も今回、ブースを出展しました
ブースの設計などはあまり情報が無く、ワールド系の技術に馴染みのない人や、VRのことをよく知らないけど出してみたいという人も居ると思います
作成時に何を考えていたかメモを取りながら進めていたので、その中から役に立ちそうな情報をまとめて共有したいと思います

設計面はnoteに、技術面はQiitaに置いてありますので興味がある方をどうぞ

* [VR空間での作品展示：誘導と設計編](https://note.com/towel_funnel/n/n9c036df6a07f)
* VR空間での作品展示：タイリングとshaderで容量を節約（この記事）

# 実物を見たい人へ
この記事にはブースのネタバレが含まれます
[実際の出店ブースはラグーナフィオーレの１２番になりますのでネタバレの前に見たい場合は先にどうぞ](https://vrchat.com/home/launch?worldId=wrld_18858862-d080-4b4a-a1fc-7d62ba7a2bfa&instanceId=81353%7Ehidden(usr_ff78c860-fb39-485c-85a9-0e03c582c9d7)%7Eregion(jp)%7Enonce(bc0b983a-ce20-41ce-bdc6-79e754eb7314))

VR機器なしでPCの画面から無料で見に行けますので、VRChatをまだ遊んでいない人はこの機会に遊んでみてください
[Steamさえインストール](https://www.dospara.co.jp/monotech/review/2948.html)されていれば、ライブラリから「VRChat」と検索してゲームをインストールすれば、こちらのリンクからすぐ見に行くことができます

# テクセルデンシティとタイリング

テクセルデンシティ（テクスチャの密度）が少なくなってくると、一気に安物感が増してしまいます
どの程度粗くてよいかはカメラがどれだけ近づくかに依存します
通常のゲームなどではカメラの位置が壁に接近する最小距離がわかるのでそれを元に考えられるのですが、VRは困ったことに角度も位置も事実上無制限かつ、自然な動きで壁や床に近づいてしまうため、既存の3Dコンテンツよりかなり細かい密度が求められます
![空白の図 - 21ページ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/b8bf6f2d-50f4-871e-0941-608c811a9e0d.png)
一定容量でテクスチャを細かくしたい場合、建築的なモデリングではテクスチャのタイリングがとても重要になってきます
しかし小さいサイズのテクスチャをタイリングしすぎても繰り返しが目立ってしまい、リアルさが減ります
今回のブースの床は512x512という小さいテクスチャですが、フローリングタイル1つ1つのUVをランダムにズラすことで繰り返しパターンを消しています
クリックして拡大してみてください
![空白の図 - 22ページ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/396326ba-d75c-0cae-430c-68d3c9d94a73.png)
# ドローコール数の戦いとテクスチャ配列

みんなの作品を集めて公開するタイプのワールドではドローコール（Pass call）の数が問題になってきます
近年のGPUではポリゴン数などよりドローコール数が負荷に影響するのですが、マテリアルを共有できない参加型ではバラバラにドローコールが発生してしまいます
そこで大抵は１ブースあたりのコール数及びバッチ数が決められていて、Vket6ではコール数が20までとなっています

コール数は色々な要素が絡んでくるものの、極論するとオブジェクト数が少なければ抑えることができます
なので、できる限り1メッシュに結合したいのですが、別々のテクスチャをどう繋げるのか？が問題になります
有名な方法として[MeshBaker](https://assetstore.unity.com/packages/tools/modeling/mesh-baker-5017?locale=ja-JP)を使ってアトラスを作成してしまう方法があります
MeshBakerはうまく作業フローに組み込むと非常に強力なツールなのですが、唯一の弱点としてタイリングテクスチャに対応していないという点があります
タイリングテクスチャをMeshBakerに入れると、タイリングが解除されて大きなテクスチャになってしまいます
これはアトラス化したテクスチャをタイリングすると隙間ができてしまうためですが、これではせっかくの容量削減が無駄になってしまいます

そこでタイリングを使う場合はテクスチャ配列を使います
簡単にテクスチャ配列を使えるツールとしては[One Batch](https://assetstore.unity.com/packages/tools/utilities/one-batch-128259?locale=ja-JP)が、配列ファイルが小さくてオススメです
[Amplify Shader Editor](https://assetstore.unity.com/packages/tools/visual-scripting/amplify-shader-editor-68570?locale=ja-JP)にもテクスチャ配列作成ツールが入っているのですが、こちらで作ったテクスチャ配列はビルド結果は一緒ですが、ビルド前の中間ファイルが重く、納品フォルダの容量制限に引っかかりやすいので注意が必要です
とはいえAmplify Shader Editorで色々やりたいこともあったので、今回のブースでは2つのツールを併用しました

ちなみに、Blender上でいきなりオブジェクトを結合するのはやめたほうがいいです
手戻りがあったときの作業がつらいことになります
Blenderでオブジェクトを残しつつ出力FBXのメッシュをまとめる場合は[Bundle Exporter](https://3dnchu.com/archives/bundle-exporter/)がオススメです

# ライトマップ

Unlit系ブースあるいはアニメ系ブース以外はライトマップを焼いたほうが良いです
ライトマップを綺麗に焼くにはUV2を指定する必要がありますが、このあたりのテクニックは検索すると出てくると思います
とはいえVket6のブースでは512x512のライトマップ１枚しか許されていないので、あくまで全体の色合いをなじませるためのものと割り切って使います

Unityライトマップを使わずに、ライトをBlenderなどでで完全に焼いてしまうのもひとつの手で、これなら高解像度の影を出すことができます
この場合ブース内外の干渉がなくなるので、色合いが浮いて見えてしまう危険性があり、そうならない調節、あるいはそうなっても良い設計を考える必要があります

# AOベイク

落ち着いたデザインでクオリティを高く見せたいのであれば、AOベイクは大切です
特に強い光のない環境ではAOはGIとほぼ同義の効果を持つので、入れるだけでクオリティの高いライト設定をしたように見せかけることができます

比較画像を作ったので、印象が全然違うのを感じ取ってください
両方ともライトマップは焼かれていますが、Vket6の規定上どうしてもUnityだけでは強い影は出しにくいので、ベイクしたAOの影響のほうが大きくなります
AOが適切に入っていない上の画像は、特に踏み台の足の構造や接地感、後ろの木の柵の端っこがなどが一昔前の安い3Dゲームみたいな雰囲気になってしまってます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/7a82abae-513b-8558-1a38-f6363046aad1.png)
よく勘違いされますが、AOは物体の色を暗くするだけの機能ではありません
囲まれた構造などで光の反射を減らすという効果があります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/510cfbbc-d986-a249-aaa9-aea2eab1d020.png)
AOが無いと反射光が角度だけで決定されてしまうので、オブジェクトが重なっている部分でも不自然に明るくなってしまうことがあります
金属の多いSF系デザインで建造物の溝が空を反射して不自然にテカテカ光ってしまうような場合にもAOは威力を発揮します

# 4つのUVの割り当て

テクスチャ配列を使う場合にはその配列の番号を指定する必要があります
よく使われるのは頂点カラーですが、正確に指定するツールがあまり無かったのでUVで指定することにしました
UVを指定する場合[Asset Creation Toolset](https://mrven.gumroad.com/l/hPXIh)というプラグインのUV管理ツールが必須レベルです（もっというとUVの並び替え機能がほしい・・・）

ワールド制作ではUV2という単語をよく聞くと思いますが、モデルには複数のUVをもたせることができます
普段使っているのがUV1で、レイヤーのように別のUVを持つことができ、shaderで読み込むことができます

UnityではUV1～4の4つを使うことができます
一般的に次のように使われます

<dl>
<dt>UV1</dt>
    <dd>通常のテクスチャ座標の指定として使われる</dd>
    <dd>実質の自由枠でshaderで自由に使える</dd>
<dt>UV2</dt>
    <dd>ライトマップの座標としてUnityが書き込み、利用する</dd>
    <dd>もともとUV2に座標が入っている場合はUnityはそれを参考にする</dd>
    <dd>ライトマップを使わない場合には自由枠になる</dd>
<dt>UV3</dt>
    <dd>リアルタイムGIのライトパス焼き込みに使用される</dd>
    <dd>リアルタイムGIを使用しない場合には自由枠になる</dd>
<dt>UV4</dt>
    <dd>自由枠</dd>
</dl>

Vket6の会場ではライトマップはベイクされ、リアルタイムGIを使用しないため、shaderで扱えるのは、UV1、UV3、UV4ということになります
（ただしUV2を設定しておいたほうがライトマップが綺麗になるので、設定はします）

なお、Batching staticを使用すると、UV2以降の座標は保証されなくなるらしいので注意してください
十分にマージされたメッシュならBatching staticは使わなくても問題ありません

この３つのUVを使って、テクスチャ用UV、AOベイクのUV、テクスチャ種類の指定、の３つをモデルに持たせます
一般的にテクスチャ用UVとAOベイクのUVは同じものを使って問題ありませんが、今回は床のタイリングのランダム化などのために別にする必要がありました
普通に考えればUV1をテクスチャ用とするのですが、AOベイクを行うときに、Substance 3D PainterではUV1のみにしか対応していません
そこで変則的ですが、UV1をAOのために使い、最終的には以下のようになりました

<dt>UV1</dt>
    <dd>AOベイク</dd>
<dt>UV2</dt>
    <dd>ライトマップ</dd>
<dt>UV3</dt>
    <dd>通常テクスチャ</dd>
<dt>UV4</dt>
    <dd>テクスチャ種類のインデックス指定</dd>


## UV1 → AOベイク
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/82015819-f726-20d0-d1df-682a23ddf707.png)
オーバーラップをしてはいけないのでしっかりシームを切って、透明なもの以外のすべてのUVを１枚にまとめます
並び替えには[UVPackmaster2](https://3dnchu.com/archives/uvpackmaster-2/)が便利でした
解像度が高い予定なので、隙間はあまり開けなくても問題ありません

この床のUVでは、Topカメラからの投影を使って、隣の板はちゃんと隣に来るように配置しています
AOは近くのものの陰影を反映するものなので、構造的に近いものを隣に置くことで自然に繋がります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/68f4846f-0a8b-ba17-d6b7-ca904d90cf9b.png)
AOベイクはBlender上で行ってもいいのですが、Blenderはベイクにはデノイザが機能しないらしく、ザラザラの質感になってしまうのと、ベイクの速度的な問題でSubstance 3D Painterのベイクを使用しています
ただし、Substance 3D Painterのほうはシームの切れ目が目立ってしまうという弱点があります（この解決方法知ってる人が居たら教えて！）
なるべくシームを裏側や見えにくいところに持っていって隠すのが重要です

AO対象をFBXに書き出し、Substance 3D PainterでAOベイクします
このFBX自体はライトマップを適用するものとそうでないものが混在しているためUnity側では使用しません
ベイクしたテクスチャだけを使います

追記
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">シーム切れ目の問題は、ハードエッジかつUVが接続されていると起きる。ハードエッジの部分はUVを分ける必要がある。<br><br>＞ただし、Substance 3D Painterのほうはシームの切れ目が目立ってしまうという弱点があります（この解決方法知ってる人が居たら教えて！） <a href="https://t.co/h2QHq4z0od">https://t.co/h2QHq4z0od</a></p>&mdash; まじかる☆しげぽん@VRoid (@m_sigepon) <a href="https://twitter.com/m_sigepon/status/1426916652466147329?ref_src=twsrc%5Etfw">August 15, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## UV2 → ライトマップ

ライトマップのUVは、AOベイクとほぼ同じです
小さいオブジェクトはAOベイクのみして、ライトマップを焼かないほうが綺麗になります
そのためUV2に並べるオブジェクトはUV1よりも少し少なくなっています

解像度はVketの入稿規定によりAOベイク用のテクスチャよりもかなり小さくなります
そのため隙間はAOベイク用よりも広く取ります
なお、今回のブースではこの隙間のとり方をミスっているため、壁の奥にある窓の周辺に変な光が漏れるミスをしています

重要な影が出るような壁などはUVを大きく取ります

## UV3 → 通常テクスチャ

通常テクスチャといっても、今回はトリプレナーを適用するプロップが多いので、床などのタイリング位置を細かく指定したいオブジェクトにのみ関係します
それぞれバラバラのテクスチャが割り当たるので、隙間とオーバーラップは気にする必要がありません
重要なのは位置関係をバラバラにすることと、UVの向き、大きさです
床のUVはこのようになっています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/322d9328-f65e-2b1b-1a14-e216b82dc277.png)
これによって、**テクスチャのタイリング位置がすべてバラバラにズレて、床の繰り返しパターンが見えなくなります**

## UV4 → テクスチャ種類のインデックス指定

ここの項目は、 **実験的でやりすぎな手法を使っているので、そのまま真似するのはおすすめしません**
使えそうな部分だけ参考にしてください

UV4で種類を分割するために、丁度いいのでUVグリッドを使います
Googleスプレッドシートに種類とパラメータを入力し、**shaderパラメータを出力するプログラムをAppScriptで組みます（馬鹿）**
それをAmplify Shader Editorにエクステンションスクリプトとして読み込み解析して、トリプレナーやノイズ、色変更など使用するすべての技法をまとめて組み込みPBR shaderにします
![空白の図 - 24ページ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/5b3697a6-f155-16a0-6655-17e51a46ebed.png)
結論としては、色々やりすぎて後半身動きが取れなくなったので、やりすぎないほうがいいです


# 法線ベベル

一部ではコリアンベベルと言うらしいですが、あんまり国名が入った技法はよろしくないと思うので、フェイクベベルとか法線ベベルと呼んでいます
メッシュ的にはベベルがかかっていないけど、法線にだけベベルがかかっている手法です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1376552/ad69386b-7e5a-62c9-f799-69d29e704ba4.png)
Blenderでベベルモディファイアから、セグメントを2、断面シェイプを1.0にして法線のハード化をON、最後にオブジェクトデータプロパティの自動スムースをONにすると作れます

既存のシームを破壊しにくいことと、並んでいるオブジェクトにベベルによる隙間を発生させずに情報量を増やすことができます
床の継ぎ目の凹みはこの手法で作っています

追記
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">法線ベベル。CG界隈だとサポートエッジって呼んでますね。</p>&mdash; 633B+ (@633B) <a href="https://twitter.com/633B/status/1426865109222100995?ref_src=twsrc%5Etfw">August 15, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
# ノーマルマップの注意点

Vket6の会場では（おそらく負荷の観点から）ディレクショナルモードがNon-Directionalに設定されています
技術的な詳細はこちらのURLを見てもらうとして
https://docs.unity3d.com/ja/2019.4/Manual/LightmappingDirectional.html
端的に言えば、ノーマルマップがあまり期待通りに出ません

回避策としては

* そもそもメッシュを割ってしまう
* ノーマルマップの影響はリフレクションに対して強く出るのでラフネスを下げてしまう
* ノーマルマップのチャンネルを加工して白黒の値にしたあとテクスチャに乗算してしまう

といった手があります

# リフレクションプローブとライトプローブ

このあたりは、適切に設定するとしか言いようがありませんが、リフレクションプローブはBoxProjection一択だと思います

Vketのノーマルが出にくい環境ではリフレクションプローブのintensityを強めに設定しても良いかもしれません
ただしこの場合は適切なAOマップが無いと変なところが光ってしまうので気をつけてください
反射が一定すぎる場合はラフネスにノイズを加えると良いです

# 設計について

[ブース全体の全体像と設計思想についてはnoteに書かれています](https://note.com/towel_funnel/n/n9c036df6a07f)
こちらもどうぞよろしく