レポート一覧
- ここが変わる!Unity 5のスマホ開発 ~アセットバンドル、ビルド、プラグイン~
- コロプラUnity 5プロジェクト ~白猫プロジェクトにおけるUnity 4からUnity 5への移行事例~
- Unity 5グラフィックス機能使いこなしガイド
- Rederer Massive Amount of Objects in Unity
- 協力・対戦ゲームを支えるPhotonの最新情報、Mecanimにネイティブ対応したPUNの実力協力・対戦ゲームの市場感
- ゲーム開発の始め方、育て方、終わらせ方~「Game Dev Heroes」における生産性チャレンジ~
講演ファイル
https://docs.google.com/presentation/d/1xeu4nnEtFqyJD68SSUTqIZleUoC2aCk3cGpG0kxvfg4/edit?usp=sharing
http://japan.unity3d.com/unite/unite2015/files/DAY1_1700_room2_Ishibashi.pdf
本講演が目指すところ
-
Unityで
-
いろんなトリックを用いて
-
多数のオブジェクトを
-
効率的に描画&更新する
-
検証はほぼPC上
-
PS4/XboxOneくらいのスペックを推定
Topics
- レンダリング
- Graphics.DrawMesh()
- 疑似インスタンシング
前提
- もっともストレートな方法:1つ1つGameObject化
- 10,000ともなると1つ1つGameObjectとして扱うのは非現実的
Graphics.DrawMesh();
- Meshを描画するリクエストをrender queueに積むAPI
- ただし、batchingは効かない
- 数百単位であれば対処可能だが、万単位は厳しい
- drawcallを抑えつつ大量のオブジェクトを描く手段が必要
- 疑似インスタンシング
- ハードウェアインスタンシング
疑似インスタンシング
- 1つのMeshに多数のモデルを格納することで1回のDrawMesh()で多数のモデルを描く
- 65,000モデルの頂点数を1回のdrawcallで書ける
- 65,000=1つのMeshがもてる最大頂点数
- 各インスタンスの情報をGPUに送り、頂点シェーダで各モデルを変形
TRS行列など
- 1つのMeshに多数のモデルを格納
- 元の頂点データを繰り返す
- 何番目のモデルか、という情報をどこかに付与(例ではuv2)
- 必要なbatchの数だけMaterialも用意
- この数+モデルIDがインスタンスIDになる
- 例:Cubeを10000個書きたい場合、10000/2708で4つ
- 各MaterialにインスタンスIDの開始番号を付与
- 必要数を超えたモデルは頂点シェーダで画面に出ないように加工
- 1個書きたい場合でも格納されているモデルの数分処理される
- vertex.xyz = 0.0
インスタンス情報をGPUへ
-
何通りかの方法があり、一長一短がある
- テクスチャにプラグインから書き込む
- テクスチャにMesh経由で書き込む
- ComputeBugger
テクスチャにプラグインから書き込む
- RenderTexture(ARGBFloat)を使用
- Textur2DはFloatのフォーマットをつかえない
- テクスチャにデータを書き込むプラグインを用意
- Texture.GetNativeTexturePtr()でテクスチャオブジェクトを取得
- ネイティブAPIを用いてデータを書き込む
- 頂点シェーダからはtex2Dlod()でデータを取得
- 速いがプラグインを描く必要あり
テクスチャにMesh経由で書き込む
- データ転送用シェーダを用意
- データ用RenderTextureをレンダーターゲットに指定
- Meshにインスタンス情報を書き込む
- 注意点:vertices, uv以外は暗黙的にnormalizeされたりする
- Graphics.DrawMeshNow()
- UnityのRender queueを経由しない
- 遅いがとてもポータブル
疑似インスタンシング ComputeBuffer
- ComputeBufferは現状Direct3D11&PS4のみ対応
- 任意のデ-タ構造の配列を格納可能
- データの更新はComputeBuffer.SetData()を呼ぶだけ
- 頂点シェーダからはStructuredBuffer<>としてアクセス可能
- 環境を選ぶが早くて簡単
ハードウェアインスタンシング
- 1回のDrawCallで1つのモデルを複数描く機能
- 頂点シェーダの入力にインスタンス別のデータが加わる
- TRS行列などを渡す
- 頂点データは1モデル分で済む
- Direct3D9~,OpenGL 3.1~,OpenGL ES 3.0~, WebGL 2.0~
- UnityではGraphics.DrawProcedural()が該当
- ただし、いろいろ使いづらい仕様になっている
- Direct3D11対応環境/PS4でしか使えない
- Meshをそのまま描くことはできない
- 頂点シェーダの入力は頂点IDとインスタンスIDのみ
- Unityのrender queueに載せることができない
- 読んだその時点で即座に描かれる
- surface shaderが使えない
対処方法
-
Meshのデータをそのまま渡すことはできない
-
ComputeBufferに頂点データを格納することで対処可能
-
頂点シェーダで頂点IDとインスタンスIDから実データを参照
-
Unityのrender queueに載せることができない
-
独自のRenderTextureに描いて結果をマージすることはできる
-
surface shderが使えない
-
deferredであればG-Bufferさえ生成できればライティングは共通処理を使える
-
Unity5.1でCommandBuffer.DrawProcedural()が追加
-
これによりrender queueに載せられるようになる
-
G-Bufferの生成、ライティングしないオブジェクトには必要十分
Graphics.DrawProeduralIndirect();
- 描くインスタンスの数をComputeBufferから読み取れる
- GPU Particleなどに最適
疑似インスタンシング:応用
- DrawProcedural()っぽいことをDrawMesh()で実現
- 頂点IDとインスタンスIDのみを格納したMeshを用意
- それらを元に頂点シェーダで実データを取得して描画
まとめ
- 良いところ
- 今現在のUnityで使える
- Unityのrender queueに載せられる
- surface shaderを使える
- 悪いところ
- 頂点データが肥大化する
- 超過分のモデルも処理される
Update
- 万単位のオブジェくとをどうUnity上で扱い、どう更新するか
- オブジェクトのデータはstructの配列として持つ
- UnityのColliderとのインタラクションなどは独自に実装
- MeshCollider以外の実装それほど難しくない
- 当たった場所にAddForce()することでRigidBodyに力を伝達
いくつかの実装アプローチ
- ネイティブコード
- ComputeShader
- C#でがんばる
ネイティブコードで実装
- 数値計算の類はC#は苦手
- ネイティブコードで適切に実装すれば10倍以上は速くなる
- マルチスレッド化
- SIMD化
- データ構造の最適化(SoA化)
- C/C++のほか、HPC向け言語も有力な選択肢
- Inter ISPC
- OpenCL(IntelがCPU実装やコンパイラを用意)
- 描画用データはRenderTextureに格納
- Texture.GetNativeTexturePtr()
- MonoのAPIを用いる方法もある
- C++からC#のUnityEngineのAPIを呼ぶ
- ComputeBufferにデータを移すには現状これしかない
- Monoを介するため低速
ComputeShader
- PC,PS4/XBoxOneで有力な選択肢
- Unity 5.1でOpenGL系の環境も対応
- ゲームロジックには影響しないエフェクト類に最適
- パーティクルの更新から描画までGPUで完結できるため高速
- ゲームロジックに影響するものにはやや不向き
- CPU側にデータを転送するのが大きなロスになる
- GPU側のスペックの上限下限の幅の広さも問題
- ComputeShaderならではのリッチな表現
- G-Bufferを利用したスクリーンスペース当たり判定
C#でがんばる
- プラットフォームによってはこれ以外選択肢がない
- オブジェクトのデータはstructの配列として持つ
- できるだけclassを参照しない書き方にする
- classは参照するだけで結構な負担になる
- 大きなデータのコピーは遅いので極力避ける
- ThreadGroup.QueueUserWorkItem()による並列化
- いわゆるタスク並列
- 注意点:Unityの機能の大部分はメインスレッド以外からは触れない
- メインスレッドで必要なデータを集めておくなどの工夫が必要
- 最新の.NETである程度改善されている
まとめ
- 現状大量描画は疑似インスタンシングが有力な選択肢
- ハードウェアインスタンシングはUnity5.1以降に期待
- アップデート処理は可能な限りC#を避けつつ自力で頑張る