3Dモデルの課題はテクスチャによる消費メモリと描画負荷

saturday06氏:こんにちは。『VRoid Hubにおける3Dモデルの最適化』についてエンジニアのMogiが発表します。新規事業部VRoid部エンジニアのMogiと申します。「VRoid」関連の開発を行なっています。趣味でオーボエという楽器をやっています。

今回の発表の題目となる「VRoid Hub」についてです。VRoid Hubは3Dキャラクターの投稿・共有プラットフォームです。投稿した3Dキャラクターが、まるでそこで生きているかのような体験の提供を目指しています。

VRoid Hubを取り巻く周辺サービスを含めた図がこちらです。アップロードされた3Dキャラクターは、ユーザーの許可があればAPI連携により各種アプリケーションで利用可能です。ここで問題になるのが、さまざまな面での3Dモデルの負荷です。

VRoid Hubで扱う3Dモデルのテクスチャはとても多いことがあります。こちらはVRoidのサンプルモデル桜田史利矢で使われているテクスチャです。これらのテクスチャをそのままメモリに展開すると、95MBになります。単純計算で、同じようなモデルを10人表示しようとすると1GB近くなり、メモリが少ない端末では処理ができなくなってしまいます。

ほかにも3Dモデルは描画や、そのための計算負荷が大きくなりがちという問題があります。例えば色の違う髪束一つひとつに対して、個別に描画負荷が発生したり、物理演算などでも重くなったりします。負荷が高いとフレームレートが下がってユーザー体験が悪くなります。特にVRにおいては顕著です。

負荷対策のために実践したこと

VRoid Hubと連携したアプリケーションの1つとして、オンライン同人誌即売会「NEOKET」があります。現実の同人誌即売会を再現したうえで、さらにグレードアップして、できるだけ多くの人たちと同時に参加できるアプリケーションを目指しています。そのために、負荷対策は非常に重要です。

負荷軽減のために、VRoid Hubにアップロードされたモデルに対して、次の変換を実行する処理をサーバーサイドに実装しました。1つ目はメモリ負荷の軽減です。テクスチャ画像に対して各プラットフォームに適した圧縮をかけます。

2つ目は描画・計算負荷の軽減です。VRoid Mobile製の結合可能なマテリアル同士を、結合前と同じに見えるように保ったまま結合します。

これらがサーバーサイド処理に用いる主な利用技術です。サーバーサイド処理をするためのアプリケーションの開発環境は、Unityを用いました。本来はゲームを作るための開発環境ですが、コマンドラインで動くプログラムを作れます。各種処理結果の目視確認がUnityのUI上でできますし、ある程度であれば自動化も容易になります。

次は、VRM入出力のUniVRMというライブラリです。UniVRMは変換対象の3DフォーマットであるVRMを扱う際のデファクトスタンダードのライブラリで、利用実績が非常に多いエクスポーターが含まれています。

また、Debian GNU Linux上での動作を想定しています。

テクスチャの圧縮とマテリアルの結合でメモリの負荷を軽減

1つ目のテクスチャの圧縮です。サーバーサイドでテクスチャを圧縮することによって、メモリの負荷を減らせます。例としてDXT5テクスチャの圧縮を挙げます。生データで16MBだったものが、圧縮して4MBになりました。

プラットフォームやブラウザーで対応している圧縮形式が違うので、それぞれに適した形式を用います。また、アルファのありなしで形式も多少変わります。圧縮率は形式ごとに違いますが、概ね半分から8分の1くらいになります。

PVRTCのところだけカッコが付いていますが、今回の用途でPVRTCは品質に関する問題が出てしまったため、現在は利用していません。

もう1つの変換がマテリアルの結合です。これを行うことにより、プログラムからGPUに対する描画呼び出しの回数を減らせます。例えば図のように、青、赤、緑の描画命令が個別に発生しているものがある場合、似たような設定のマテリアルを結合できます。

またテクスチャだけ違うマテリアルは、アトラス化という手法を用いて結合できます。アトラス化というのは、複数のテクスチャを1枚のテクスチャにまとめる技術です。これによって、先ほどは3回だった描画呼び出しが1回に減ります。

描画命令の呼び出しの負荷は、UnityエディターのSetPass callsという指標で確認できます。VRoid Mobileで作ったモデルに対して、結合を行った結果を示します。結合前は34だったものが、結合後は20になっています。

現在はVRoid Mobileによって作成されたモデルのみマテリアルの結合に対応しています。これはVRoidの開発チーム側が、どのようなマテリアルが存在するかを完全に把握しているからです。徐々に結合できるモデルの種類を拡大する予定です。

これらの変換にかかる時間です。プログラムの起動に1秒で、マテリアルの結合に時間がかかる場合は20秒くらいかかります。

テクスチャの圧縮は非常に時間がかかるため、例えばそれらを軽減するために、テクスチャサイズが大きい場合、はじめにテクスチャのサイズを縮小してからプラットフォームごとの圧縮を行うなどの工夫をしています。

そのほかが10秒くらいで、合計で100秒以内の時間がかかっています。ちょっと時間がかかりすぎなので、改善を進めています。

サーバーサイドでUnityを動かす方法

ここまではサーバーサイド変換の中身の解説でした。ここからは、サーバーサイドでUnityのアプリケーションをどう動かしているか解説します。

変換プログラムを、数種類の異なるLinux上で動作させる必要が出てきました。サーバーレス環境での動作ですが、この環境でDockerなどのコンテナ技術は使えませんでした。

また、一定規模以上の会社でUnityのアプリケーションをビルドするには、ライセンス認証が必要になるので、複数のターゲット向けのビルドを作る場合、負荷が高いです。そのため、一度ビルドしたらどこでも動作するUnityアプリケーションが欲しいと考えました。

では、どうやってビルドするかを説明します。LinuxサーバーでのUnityアプリケーションの実行環境の模式図はこんな感じです。右から、一番えらいLinux Kernelがあって、その上にGNU C Libraryという基本的なシステムの関数を実装しているライブラリがあります。

その上にUnityのアプリケーションが乗っかって、そのUnityのアプリケーションがGUIやOpenGL、GPUのエミュレーションを叩きます。それぞれはXvfb、Mesa、LLVMpipeが担当します。

Unityのアプリケーションをポータブルで別の環境にもっていけるようにするためには、Linux Kernel以外のものを自前で準備する必要があります。自前で準備するというのは、つまり自前でビルドするということです。

ビルドに必要なライブラリがfreedesktopのページにまとまっています。ものすごく多いです。CSVにソースコードのURLやビルドの設定を書いて、がんばって管理しようとしています。これでどうにかビルドできました。

ビルドできたら、次は別の環境にもっていきます。別の環境として、別のGNU C Library、別のLinux Kernelという登場人物が現れました。現在のカーネルとライブラリを抜いた状態で、別のLinux Kernelが僕らの作ったUnityのアプリケーションを起動しようとしている模式図です。

しかし、このままでは起動しませんでした。別のLinux Kernelが、Unityのアプリケーションを起動しようとしますが、別のLinux Kernelにくっついている別の GNU C Libraryが、いつもと別物だぞとエラーが発生してクラッシュしてしまいました。

こういう場合の回避方法がGNU C Libraryに存在しています。ld.soというコマンドを使うとで、別のGNU C Libraryになり代わってプログラムを実行できます。これにより、Unityのアプリケーションが正しく実行できました。

実行結果はこちらです。Unityという単語と、GPUエミュレーションを行なっているLLVMpipeなどの単語が確認できます。この表示のあとに、先ほど説明したテクスチャ圧縮などの変換が行われます。

今後もさまざまな変換処理の実装を行っていく

まとめです。今回はテクスチャの圧縮によってメモリ負荷の軽減を行いました。また、マテリアルの結合による描画の負荷の軽減を行いました。それらを実行するために、Linux上にポータブルなUnityアプリケーションの実行基盤の構築しました。

今後は、メッシュの削減や、VRoid Mobile以外のあらゆるモデルに対するマテリアルの結合や、その他さまざまな変換処理の実装を行います。また、100秒以上かかっている変換の高速化も行う予定です。

アプリケーションの実行基盤に関しては、より安定した、よりスケールできるサーバーサイドのUnityアプリケーションの実行基盤の構築を行います。Dockerなどの利用が可能になったため、Hackに頼らない方式を用いられそうです。

以上です。ありがとうございました。