Monday, April 27, 2015

언리얼엔진 4, 소스코드로부터 APK 생성 후기

무료화된 언리얼 엔진4로 APK를 만들어 보았습니다. GitHub로 제공되는데 PRIVATE리포지토리라서 언리얼 엔진 공식 사이트에서 회원등록해야 리포지토리에 접근이 됩니다.

소스코드 빌드와 에디터 실행


빌드 방법은 GitHub의 Readme.md에 다 나와 있어서, 지시대로 VisualStudio 2013로 빌드하고 실행하면 에디터가 뜹니다.


샘플 실행중의 모습.


안드로이드 바이너리 만들기


공식  https://docs.unrealengine.com/latest/KOR/Platforms/Android/GettingStarted/index.html

안드로이드 APK로 패키지합니다. ETC1텍스쳐를 지정하면 단말기를 가리지 않고 실행 가능할 겁니다...



missing UE4Game binary이란 에러가 떴습니다. 사전에 Visual Studio로 안드로이드 바이너리를 빌드하지 않으면 이런 에러가 뜹니다.
(참고: 옛날 버전 얘기입니다. 세상이 좋아져서 최근 버전은 에디터로부터 바이너리 생성까지 다 해줍니다! 따라서 아래 Visual Studio작업도 필요 없습니다.)



UE4.sln에서 Solution Configurations에 "Development", Solution Platforms에 "Android"를 지정해서 빌드하면 안드로이드 바이너리가 생성됩니다. 다시 에디터에서 패키지해 보니 APK가 생성되었습니다. 단말기에 APK를 전송해 주는 배치파일까지 만들어 주는 센스!



APK가 25MB, Obb파일이 1GB나 되네요. 깔아 보면 단말기에서는 100MB정도가 됩니다.

결론


언리얼엔진은 자료가 아주 잘 정리되어 있어서 APK까지 쉽게 만들 수가 있었습니다. 단점은 엔진 자체 크기 때문에 빌드 시간이 아주 깁니다. 소스코드 빌드 시간이 걸리는 것은 어쩔 수 없는데, 간단한 APK 생성이 30분 정도 소용이 됩니다. 언리얼 개발을 위해서는 하이엔드PC부터 맞추어야 될 것 같습니다.

그리고 어떤 단말기에서 실행이 안 되었습니다. Acer Iconia B1-730HD에서는 돌아갔는데, 팬택Vega Racer에서 안 돌아갔습니다. 하지만 TAPPY CHICKEN는 Vega Racer에서 실행되었으니 조정을 잘하면 될 수도 있겠네요...

Tuesday, April 7, 2015

OpenGL ESでのUniform Bufferのバインドの仕方(DX11比較付き)

Unifrom BufferはDirectX11のConstant Bufferとほぼ同じものです。OpenGL ES 3.1では使い方もよく似ていて、以下のようにGLSLの"binding"で指定するbinding pointがほぼHLSLの"register"と同じ概念です。



C側からバッファを"register"に指定するID3D11DeviceContext::VSSetConstantBuffers に相当するのは、glBindBufferBaseです。



OpenGL ES 3.1はここまでで動作します。

GLSLで"binding"が使えないOpenGL ES 3.0では以下のそれ相当の処理をC側から行う必要があります。これはOpenGL ES 3.1でも有効で、"binding"で指定するのと同じ結果になります。更には、既に"binding"指定があってもC側から上書きすることもできます。

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

Saturday, February 28, 2015

Visual StudioとAndroid StudioでOpenGLを使ったマルチプラットフォーム開発

実際に構築してみました。ここにコミットしています。
ソースを共用しながら、AndroidとWindowsの両方で動かせるようにしてみました。C++を主に使うのでNDKも入れておきます。

Android StudioでassetsフォルダやC++のソースを階層の浅い場所に移動


Androidのassetsフォルダにテクスチャやシェーダーを配置します。ただ、eclipse時代と違ってAndroid Studioはデフォルトで app/src/main/assetsとすごく深い場所にあります。これでは不便なので、build.gradle に以下のように書いて使いやすい場所を指定します。(同時にC++のソースも浅い場所に移動しています)


main.assets.srcDirsはassetsを置く場所、main.jni.srcDirsはC++のファイルを置く場所です。'../../assets'のようにAndroid Studioのプロジェクトファイルより上位のフォルダも指定できることがわかりました。

Visual Studioからも同じソースやファイルを使うので、この任意のフォルダ位置を指定する機能は重宝します。


assetsフォルダをC++から使う


AndroidのassetsはC++から直接アクセスできないので、Javaを経由します。以下のようなヘルパークラスを作っておき、C++から使います。

Javaでは指定のファイル名でassetsフォルダから読みだしてバイト列にしてC++側に返します。C++側でJavaからバイト列をもらい、改めてcallocでメモリを割り当て直してコピーしています。callocでサイズを+1しているのは、テキストファイルの終端に'\0'を入れたいためです。


テクスチャをJava経由でC++から生成


Android機はGPUによって対応している圧縮テクスチャフォーマットが違うので面倒です。この対応は後で考える事とし、とりあえず手始めにjpgやpngなどをまず使えるようにしておきます。
AndroidではJavaから直接テクスチャを生成すると簡単です。assetsフォルダからBitmapFactory.decodeStreamでBitmapを生成し、更にBitmapからGLUtils.texImage2Dでテクスチャを生成します。JavaからC++に渡すのはGLuint型(Javaではint型)の生成済みのテクスチャを表すtexture nameだけなのでシンプルに実装できます。


Windows版の対応


それぞれAndroid版と同名のローダを作ります。

LoadFileはassetsフォルダのファイルをfopenでファイルを読み込むだけです。
LoadTextureViaOSではGDI+を使うことで、アルファチャンネル付きの32bitのrawデータが得られます。ここからテクスチャを生成しています。

GDI+とOpenGLではRGBAの並びが違うのでビットシフトで置き換えています。
OpenGLは以前作ったWGLGrabberを使って関数ポインタを取得しています。


ネイティブクラスの設計


基本的に出来る限りC++で処理すると決めたので、onTouchEventを使ったタッチ等のイベントは全部JavaからC++に渡します。また、GLSurfaceViewを使うので、onDrawFrameからC++のレンダラーを呼び出すことになります。

注意すべきはJavaのスレッドが2つあることです。onTouchEventはUIスレッド、onDrawFrameはレンダースレッドです。

今回は簡単にするため、タッチの座標だけ保存しておいてonDrawFrameの中からタッチイベントもC++に渡します。こうすることでC++は常にレンダースレッドで実行されることが保証されます。

将来的にはマルチスレッドの同期取りはC++側に移動したほうが最適化しやすいかもしれません。


まとめ


OpenGLを使った開発はOpenGL関連のコードが大多数を占めるので、このようにプラットフォーム依存する場所を開発の始めに括っておくと以後のマルチプラットフォーム化が楽になります。

Wednesday, February 18, 2015

DIY: Binding Lua5.3 with C++11

Let's bind C++ stuff to Lua5.3! :)

THE MEAT AND POTATOES


It might be enough to simple scripting for several projects.


BRUTEFORCE WAY


Our main weapon is C++, so the above code doesn't satisfies me. So, let's bind C++ classes and create instances from Lua.

It works fantastic. Then MyClass is able to be generated with scripting, and the garbage collector calls our destructor and delete it.
However, it might be unpleasant chore to make additional method and classes. The bind code is too long and messy!

HACKING


Let's make stuff fun. The most brilliant thing for us is we are living in future. Now, C++11 allow us what we couldn't do before.

Below code is all to bind MyClass:


Most simple binds are able to be written with lambda. So, for compile it C++11 must be enabled. To prevent crashes by scripting bug, define a GET_MYCLASS macro to ensure the first element of the Lua stack is our MyClass. Note that MyClass instances are constructed with placement new. In this case, the destructor must be called directly instead of calling delete operator.

BindClass defined like below:


As you can see, there is no MyClass dependencies. To avoid dependencies, I made a closer generator and put the class names and userdata creators into upvalue. Of course, BindClass can also be used for binding two or more extra classes.

CONCLUSION


Binding to Lua is not so complex and easy to learn. It's only combinations of small number of features such as stack and metatables. I saw programmers bothered increasing binding codes. I bet we can simplify them. Hope this helps to find your own solution.