【Unity】Unite 2015「Rederer Massive Amount of Objects in Unity」レポート


# レポート一覧

- [ここが変わる！Unity 5のスマホ開発　～アセットバンドル、ビルド、プラグイン～](http://qiita.com/baba_s/items/8d4a30d6458b9efac2fe)
- [コロプラUnity 5プロジェクト ～白猫プロジェクトにおけるUnity 4からUnity 5への移行事例～](http://qiita.com/baba_s/items/3c394db28be90d83234c)
- [Unity 5グラフィックス機能使いこなしガイド](http://qiita.com/baba_s/items/d4f7b24bab2ccdaa1f1e)
- [Rederer Massive Amount of Objects in Unity](http://qiita.com/baba_s/items/228b368196ef606e4b68)
- [協力・対戦ゲームを支えるPhotonの最新情報、Mecanimにネイティブ対応したPUNの実力協力・対戦ゲームの市場感](http://qiita.com/baba_s/items/3f29400c026fc0d87842)
- [ゲーム開発の始め方、育て方、終わらせ方～「Game Dev Heroes」における生産性チャレンジ～](http://qiita.com/baba_s/items/a563b09e93e7e868d00b)

# 講演ファイル

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へ

- 何通りかの方法があり、一長一短がある

	1. テクスチャにプラグインから書き込む
	2. テクスチャにMesh経由で書き込む
	3. 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#を避けつつ自力で頑張る