[UE] Virtual TextureとそのStreamingの仕組み、またよく頂く質問への回答

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/9b58897d-0f68-6639-a641-0e595a226579.png)
この記事は[Unreal Engine 4 (UE4) その2 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ue4_due)の8日目の記事です。

本記事ではVirtual Textureの意味とそのStreamingの基礎的な仕組み説明した後、良く質問される内容をまとめてみようと思います。

##はじめに
Unreal EngineではVirtual Textureという機能を実装しており、UE4.26の現在、PCからMobileまで多くのプラットフォームでVirtual Textureを使うことができます。このVirtual Textureがどのような利点がありどう使うかなどは、[公式ドキュメント(UE Doc: Virtual Texturing)](https://docs.unrealengine.com/ja/RenderingAndGraphics/VirtualTexturing/index.html)に詳細に記載されております。

更に、すでにヒストリア様やAlwei様が詳細な解説記事を公開しております。ありがとうございます！
 * [VirtualTexturingについて｜株式会社ヒストリア](https://historia.co.jp/archives/14062/)
 * [UE4 ランタイムバーチャルテクスチャーとランドスケープによるメッシュ境界のブレンド(Alweiさんブログ)](https://unrealengine.hatenablog.com/entry/2020/05/30/225220)

ということで、なかなかに二番煎じな感じもするのですが。。この記事では、より動作のイメージがしやすくなるように解説できればと思います。実際の設定方法などは上記ドキュメントなどを参照していただきつつ、こちらの記事で動作のメンタルモデルが形成されることを狙っております。

## Virtual Textureとは (MipMapとの違い)
Virtual Textureとは、使う側から見れば「テクスチャの全体ではなく、一部分のみをメモリに置いたテクスチャ」と言えるかと思います。従来のテクスチャでは、必ずテクスチャ全体をメモリにロードする必要がありましたが、Virtual Textureでは必要な部分だけを読み込んで使うことができます。ですのでVirtual Textureはメモリ使用量を抑ることができる技術です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/15f1cb08-cb0c-7cc5-6463-ea0239c10f26.png)
実際にレンダリングされているテクスチャをRnderDocでキャプチャしているものです。もはやテクスチャの位置的な連続性も無視して、タイル単位で各データが格納されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/c96919ef-dd9d-8385-a794-e25d44b7c962.png)

そして、これはMipMapのあるテクスチャでも同様に利用できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/fa8c4049-9ae4-b46a-f30c-4026c8f374cf.png)
実際に、上記のVirtual Textureのタイルに近づいて見てみるとそれがわかります。下図のように、同じ大きさのタイルですが、下側が詳細なMipのタイルで、上側がそのもう一つ上の低解像度のMipのタイルが格納されているのがわかるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/432e6824-dd83-c6a7-fd90-d6e19bc2dfb1.png)

このMipMapとタイルの組み合わせにより、非常に大きなテクスチャでも、カメラに近く詳細なMipが必要な部分だけ詳細を読み込み、他の部分は低解像度のMipしか読み込まないなどをして、メモリ使用量を削減することができるというわけです。

## Virtual TextureのStreaming (and 普通のTexture Streamingの違い)
こんな便利なVirtual Textureですが、では誰が

* カメラの視点に応じて、
* 各Virtual Textureの必要な部分とその解像度を見積もり
* それをストレージからロードさせてメモリに読み込んでおく

というのを毎フレーム律儀にやっているのか？？。。。この(結構めんどくさそうな)一連の処理を、UE4は内部で実装しています。処理の流れを見比べるために、UE4の普通のTextureのStreamingとの違いを見ていこうと思います。

#### 普通のTextureのStreamingの流れ
まず、UE4の普通のTexture Streamingは、__”CPU上で”__テクスチャが必要とするMipを計算します。このチェックは、オブジェクトのメッシュではなく、オブジェクトのバウンディングボックスから簡易的に見積もられます。(CPUで各オブジェクトのポリゴンメッシュの詳細をチェックするのは非常に高コストのため、バウンディングボックスで簡略化しています)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/d747b93e-c325-ad14-c65f-8d0b2e1b56e5.png)
Texture Streamerは、指定されたMipMapをストレージからロードしてメモリに確保し、それらをテクスチャとしてGPUに送ってレンダリングします。

#### Virtual Texture の Streamingの流れ
一方、Virtual TextureのStreamingでは、__"GPU上で”__各テクスチャの必要な部分とそのMipを見積もります。GPU上で行われる実際のオブジェクトのレンダリング時に見積もるため、「カメラから見て、そのテクスチャがどれくらい近くて(どのMipで)？、どの部分が見えているか(どのタイルか)？」というのを各ピクセル毎で調査し集計し、CPU側のメモリにその結果をフィードバックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/7662cfe1-3364-7983-9e68-475704f595ae.png)
各テクスチャの必要なMipとタイルがわかったので、もしもそのタイルがメモリになければ、ロード要求を出す。。。と言う流れです。

これが、Virtual TextureのStreamingの大まかな仕組みです。「Virtual Textureにとりあえずしとけば、大きなテクスチャでもメモリ節約しながら使う事できるっしょ？」と言う人がたまにたいますが、実装を見てみると、なかなかに複雑でめんどくさそうな処理をしているのがわかるかと思います。。

そのうえで、次のセクションからはVirtual Textureに関して良く聞かれることに対しての簡単な回答をまとめていこうとおもいます。

## Virtual Textureで良く聞かれる質問とその回答

#### Q. Virtual TextureはTexture Streamingの対象外になる？
 はい。 ”対象外になる”と言って良いかと思います。上記の説明の通り、Virtual Textureは従来のTexture Streamingとは別の機構でStreamingされます。なので、Texture Streamerの管理外のアセットとなります。 (厳密には、Virtual Texture用のStreamerもUE4のContent Streamerの一つとなっています。つまり、Texture Streaming, Mesh Streaming, Animation Streamingなど他のStreamerと並列してVirtual StremaingのStreamerが存在します)

#### Q. Virtual Textureでも、普通のTexture StreamingみたいにStreamingが間に合わないと一瞬低いMipが映ったりすることがある？
はい。タイル単位の細かい読み込みになりますが、結局はVirtual TextureもStreaming技術を用いているので、残念ながら間に合わなければ低いMipでレンダリングされてしまいます。(そのかわりクラッシュなどはしないように低いMipは前もって読み込まれています)

#### Q. VirtualTextureを用いるとマテリアルが再コンパイルされる理由
理由はBasePassのシェーダを書き直す必要があるからです。変更される点は大きく二点あります。

1. 使用しているVirtual Textureがどのタイルを要求しているかを、別途書き出す処理の追加
2. 普通のTextureではなくVirtual Textureを読み込む様にテクスチャ読み込み時のコードの変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/b1041ea6-ab23-917e-edaa-7fb002c3477f.png)

この二点が変更されます。__ここから見てわかる通り、Virtual textureは副作用のない処理ではありません。シェーダコードの計算量や出力の増大から、処理負荷に影響を与える危険性があります。__使用の際はその点ご留意し、定期的なプロファイルを取ることをおすすめします。

#### Q. テクスチャをVirtual Textureとして使用する際、再度コンバートがかかるのはなぜ？

Virtual TextureとしてTextureを扱う場合、そのテクスチャは、タイルでの読み込みに適した形式に事前に変換して保持しておきます。この事前準備により、リアルタイムでのVirtual Textureの読み込みや変換を高速に行うことができます。

## その他、デバッグのための質問二つ
#### Q. VirtualTextureのタイルの境界線を表示させるには
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/9b58897d-0f68-6639-a641-0e595a226579.png)

このように、実際に見ているシーンからどのVirtrual Textureのタイルがレンダリングされているかを見ることができます。このようなタイルの境界線を表示させるには、以下のコマンドを入れてください。

```
r.VT.Borders 1
r.VT.Flush
```

消すためには、Bordersを0にしてもう一度FLushです。

余談ですが、追加でリアルタイムに境界線を描画しているわけではありません。メモリ内部のVirtual Textureにそのまま境界線を上書きしております。(下図はVirtual TextureをRenderDocでキャプチャして、一部のタイルに近寄ったもの)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363464/432e6824-dd83-c6a7-fd90-d6e19bc2dfb1.png)


#### Q. シーン内のVirtual Textureの一覧を取得できますか？
残念ながら、自分の知る限り現状そのようなツールはございません。(あったらごめんなさい!!) ListTexturesを拡張すれば簡単にできるかと思いますので、必要ならばお試しいただければ幸いです。


## 〆
ありがとうございました！

非常に簡単にですが、Virtual Textureの仕組みとそのStreamingの流れについて説明させて頂きました。Virtual Textureの動作のイメージをするのに、少しでも役立出ば幸いです。

明日は、Epic Games Japanの若手スーパーTA、小林さんの「HoudiniからUE4へのAlembic Groomインポート」です！