Saturday, March 28, 2015

Androidで動くモダンなインスタンシングを考える

このNVIDIAのスライドは基本的にPC対象です。しかし、昨今のゲーム業界はモバイル無しでは語れません。OpenGL ESで対応可能か機能を吟味してみます。

一発のDrawCallでたくさんのオブジェクトを表示するには以下の様な機能が必要です。

  1. インスタンシングのためのAPI
  2. シェーダーからバッファにランダムアクセスする方法
  3. シェーダーから複数のテクスチャにランダムアクセスする方法

サンプルプログラム程度の簡素なインスタンシングならglDrawElementsInstancedのようなAPIと、glVertexAttribDivisorでper instanceデータとして個別の座標等を渡せば済むので、2,3は無くてもいいかもしれません。しかし、マテリアルやボーンを変えてインスタンシングしたいなど、実用性を考えだすと2や3が必要となってきます。

まずは1のAPIですが、glMultiDrawElementsIndirectを使うと形の違うメッシュを一回のDrawCallで同時に描画できそうでした。しかし、残念ながらOpenGL ESでは使えなさそうです。glDrawElementsInstancedという、DirectX9時代からあった伝統的なインスタンシングで我慢します。

2と3は、マテリアルやボーンなどが異なるオブジェクトを一回のDrawCallで描くためには描画と描画の間にテクスチャの切り替えやバッファの更新を入れることができないため、描画対象の複数のオブジェクトが必要なデータを全部VRAMに上げておいて、シェーダーからランダムアクセスする必要があるということです。特にボーンが入っていて個別にアニメーションするとなると大きなバッファが必要です。

2. は嬉しいことに、OpenGL ES 3.1 ではSSBOという、ランダムアクセス可能なサイズの大きいバッファに対応しています。(資料https://www.khronos.org/assets/uploads/developers/library/2014-gdc/Khronos-OpenGL-ES-GDC-Mar14.pdf)しかし、Lollipopより前、KitKat以前のOpenGL ES 3.0の場合、uniformバッファを使う必要がありそうです。当然uniformの最大サイズに制限があるので、それより大きなバッファにランダムアクセスしたい場合はテクスチャにデータを格納してvertex texture fetchするなど更なる工夫が要求されます。

3.はbindless texture(ARB_bindless_texture)に対応していると理想的です。シェーダーからどのテクスチャにアクセスするかに関してほぼ制限がありません。しかし、OpenGL ESでは使えません。しかも、PCであっても比較的最近のものしか対応していないようです。

スライドではテクスチャをタイル化して大きなテクスチャを構成するARB_sparse_textureにも言及されていますが、同様にESでは使えません。

OpenGL ES 3.0 であればTexture Arraysが使えます。ただし、テクスチャのサイズやフォーマットを一致させる必要があるなど、アーティストに制限を課してしまうかもしれません。

古典的な手法ではTexture Atlasesや、あるいはインスタンシング対象はテクスチャを変えない、インスタンシングはしない、なども視野に入れて悩む必要がありそうです。


まとめ


PCなら以下のセットで異種混合インスタンシング
  1. glMultiDrawElementsIndirect(ARB_shader_draw_parameters)
  2. SSBO(ARB_shader_storage_buffer_object)
  3. bindless texture(ARB_bindless_texture)

Lollipop(OpenGL ES 3.1)なら以下のセットでインスタンシング
  1. glDrawElementsInstanced
  2. SSBO(ARB_shader_storage_buffer_object)
  3. Texture Arrays

OpenGL ES 3.0なら以下のセットでインスタンシング
  1. glDrawElementsInstanced
  2. uniform か vertex texture fetch
  3. Texture Arrays

OpenGL ES 2.0はインスタンシングのAPIが無いのであきらめる。

Sunday, March 22, 2015

OpenGLで異種混合インスタンシングを考える

レンダリングの高速化のためにはGPUとCPUの平行動作が重要で、一発のDrawCallで大量のモデルを描画するためにOpenGLの拡張が続けられています。2014年にはNVIDIA、AMD、INTEL各社がインスタンシングやbindless texture等の技術講義を行っていたようですね。

オーバーヘッドを減らすといえば、先日GDC2015で発表されたVulkanや、DX12の登場が待ち遠しいところですが、現状で使える最強のDrawCallはglMultiDrawElementsIndirectかと思われます。

伝統的なインスタンシングは同じモデルを大量に描画できるのに対し、glMultiDrawElementsIndirectは異なるモデルを一回のDrawCallに含められる点が頭一つ抜きん出ています。

GDC2014のスライド、
http://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead
が網羅的に解説しています。

また、NVIDIAのスライド
http://www.slideshare.net/CassEveritt/beyond-porting?related=1
はモダンな描画方法の何がいいのかわかりやすく解説しています。NVIDIAのサンプルプログラムMultiDrawIndirectは異種混合インスタンシングを実現したサンプルです。

原理は簡単で、同一の頂点バッファに複数のモデルを含めておくだけです。頂点バッファのどの部分を描画に使うかは、DrawElementsIndirectCommand構造体の変数が指し示します。glMultiDrawElementsIndirectにはDrawElementsIndirectCommandを配列で渡せるので異種混合インスタンシングができます。ただし、同じ頂点フォーマット、同じシェーダー、同じレンダーステートどうしでないと一緒に描画できません。

また、残念なのはAndroidでは現状で最も機能豊富なAndroid Extension Packに対応していてもglMultiDrawElementsIndirectは使えなさそうなことです。

AndroidではglDrawElementsIndirectがES 3.1から、glDrawElementsInstancedがES 3.0から使えます。これらは基本的に一回のDrawCallで同じモデルを複数描画することができます。OpenGL ESでどうしても異種混合インスタンシングをしたいなら、ボーンを操作したりモーフィングしたりで形を変えながら別のモデルに見せることは出来るかもしれません。しかし、基本的にインスタンスごとにインデックスバッファを変えたり頂点数を変えたりできないです。

現状では異種混合インスタンシングはPC、あるいはフルスペックのOpenGLをサポートしたSHIELD tablet等でしか使えなさそうです。


Saturday, March 7, 2015

Unreal Engine 4をソースからビルドしてAndroid用APKを作ってみる

Unreal Engine 4が無料化したので使ってみました。プログラマーなのでGitHubから取得します。無料化したとはいえGitHubではPRIVATEレポジトリなので、まずUnreal Engineの公式サイトで会員登録した上で自身のGitHubアカウントを紐付けます。すると、GitHubのUnreal Engineのレポジトリにアクセスできるようになります。

ソースコードのビルドからエディタ起動まで


必要な情報はGitHub上のReadme.mdにほとんど書いてあります。指示通りVisualStudio 2013でビルドするとエディタが起動しました。


サンプルプロジェクトを生成して実行している様子。ここまで何のエラーもありませんでした。えらく簡単です。


Android のバイナリの作り方


公式: https://docs.unrealengine.com/latest/JPN/Platforms/Android/GettingStarted/index.html

さっそく、AndroidのAPKを作ってみます。どの端末でも動くようにとりあえずテクスチャはETC1にしました。



missing UE4Game binary というエラーが出ました。アンドロイド用のバイナリをVisual Studioでビルドしていなかったのが原因です。
(追記:昔のバージョンの話です。最近はエディタからの操作だけでアンドロイド用のバイナリがコンパイル&ビルドされます。よって、以下のVisual Studioからバイナリを作る手順も必要ありません)



UE4.slnで、Solution Configurationsを"Development"、Solution Platformsを"Android"にしてビルドします。改めてエディタからパッケージしてみると、APKの生成に成功しました。インストール用のバッチファイルを実行すればAndroid端末にインストールできます。



APKが25MB、obbファイルが1GB…何かの間違いではと思いたくなるサイズですが、実際にインストールすると100MB程になります。

まとめ


この少ない手順でソースコードからAPKまで作れるのは驚異です。ドキュメント類も充実していて迷うことがありません。難点もあります。ソースコードのビルドに時間がかかるのは当然かもしれませんが、APKの生成に30分程掛かりました。本格的に開発するとなるとSSD付のハイエンドPCが欲しいところです。

また、動く端末と動かない端末があります。Acer Iconia B1-730HDでは実行できたのですが、 Adreno 220搭載の Pantech Vega Racerでは動きませんでした。端末が古すぎるせいでしょうか。ただ、TAPPY CHICKENはVega Racerでも動いたので調整次第なのかと思います。

また、Unrealで作ったゲームは実行ファイルも大きく、端末でゲームが起動するまでに時間が掛かりました。ある程度規模の大きいゲーム向けであって、カジュアルゲームにはオーバースペックということかもしれません。