メッシュアセットは頂点数分なんらかの情報を持っています。
Unityのようなゲームエンジンでは頂点のレイアウトを意識せずにメッシュを描画に利用できるように整えられていることが多いですが、その結果一頂点あたりのデータがそれなりに大きくなっているような場合があります。これは配信データの肥大化や、VRAMの圧迫、ランタイムのパフォーマンスの低下を招くリスクがあります。
以下のようなフォーマットは、一般的な描画上の要件を満たしますが、ムダでしょう。今日の商用ゲームでこのようなフォーマットが採用されることはまずありえないはずです。
そこで、次のようにしてみるとどうでしょうか?
24bytesまで削げました。半分のサイズですね。
さらに踏み込んで、座標をバウンディングボックスのサイズに合わせた分割的取扱いにして8byteにするかどうか少し考えていたのですが、若干面倒を呼び込みそうな対応のわりに4byteしか変わらないのもどうかなと逡巡しつつ調べていると、有名なグラフィクスエンジニアのSebastian Aaltonenさんが以下のような話をされているのを見つけました。
こちらでも図にしますが、提示されている16bytesのレイアウトです。(多分)
ポイントとしては、座標の数値を2bytes化してその際にパディングの都合として余る領域に法線を詰め込む点でしょう。ここまですれば全体のサイズをさらに8bytesも削減できますから、座標の圧縮をやってみてもよい気持ちになります。私もこの方針で実装してみることにしました。
さて、頂点シェーダでのデコードは大体以下のような流れになります。
実装上注意が必要な点は、バウンディングのスケールはModel To World行列の作成時に加味しておくことと、またUVが0-1に収まらない場合、そのスケールはCBで渡しておくことくらいでしょうか。
実際にドーナツ(約59万頂点)形状を描画してみたものも貼ります。右が16bytesフォーマット、左が48bytesフォーマットによる法線、Tangent、Bitangentの表示です。
変わらんように見えますね。
うん。どの方向から見ても、拡大しても目では違い、まず分からんっすね。
見た目は変わらないのですが、頂点バッファのサイズはなんと、約27MBから9MBまで落ちています。48bytesのレイアウトが16bytesになったのですから当然ですが、すごいですね。ストレージに格納されるアセットのサイズや利用VRAM量について、大幅な改善となりました。今時ポリゴンくらい沢山使わせてあげたいですからね。帯域の厳しいモバイルでは特に重要でしょう。
終わりに
なんであれ、頂点レイアウトはケースバイケースです。巨大でとんでもないハイポリゴンなメッシュなんかは、座標の精度が足りないということもあるかもしれません。それに、タンジェントが不要なケースもあるでしょう。オプショナルに頂点カラーが必要な場面もあるかもしれません。プロジェクトで利用される可能性のある頂点レイアウトが任意に増減することを前提にしてPSO事前コンパイルのシステムが用意しておかないと、面倒になるかもしれません。
本件は最近メッシュのアロケーション戦略を整えていた過程で調べた話でした。タンジェントと法線は、ベクトルベースの圧縮がスキニングなど含め全局面で一番取り扱いやすいかなと思います。一応他にも緯度経度的な表し方をする手法や、クオータニオンでの実装による圧縮を試しましたが、あんまりメリットはないかな…
★インフィニットループでは3Dゲーム開発にも取り組んでおります。興味がある方は是非採用情報をご確認ください。★