講演ファイルのダウンロード(PDF)

Render Massive Amount of Objects in Unity
Seiya Ishibashi ( @i_saint )
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
本公演が目指すところ
•
•
•
•
Unity で
いろんなトリックを用いて
多数 (>10,000) のオブジェクトを
効率的に描画 & 更新する
•
•
検証はほぼ PC 上
PS4 / XBoxOne くらいのスペックを想定
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
About Me
•
•
•
Seiya Ishibashi (a.k.a i-saint)
以前は並列プログラミングを中心にローレベル全般を担当
最近はグラフィックに絡む仕事が多い
• Candy Rock Star の床とかを担当
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
About Me
•
CPU & GPU を全力でぶん回して美しいインタラクションを実現するのが生き甲斐
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Demo
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Topics
•
•
レンダリング
• Graphics.DrawMesh()
• 擬似インスタンシング
• ハードウェアインスタンシング
• 擬似インスタンシング・応用
アップデート
• プラグイン実装
• GPGPU
• C# でがんばる
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Rendering
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
前提
•
•
•
最もストレートな方法:1 つ 1 つ GameObject 化
10,000 ともなると 1 つ 1 つ GameObject として扱うのは非現実的
• アクティブな GameObject は存在するだけで結構な負担
GameObject なしに Mesh を描く手段が必要
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Graphics.DrawMesh()
•
•
•
•
Mesh を描画するリクエストを render queue に積む API
• MeshRenderer に近い機能を GameObject なしで実現
ただし、batching は効かない
数百単位であれば対処可能だが、万単位は厳しい (drawcall 爆発)
drawcall を抑えつつ大量のオブジェクトを描く手段が必要
• 擬似インスタンシング
• ハードウェアインスタンシング
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
擬似インスタンシング
•
•
•
1 つのMeshに多数のモデルを格納することで 1 回の DrawMesh() で
多数のモデルを描く
65000 / モデルの頂点数 を 1 回の drawcall で描ける
• 65000 = 1 つの Mesh が持てる最大頂点数
• 例: Cube は 24 頂点なので 65000/24 = 2708個
各インスタンスの情報を GPU に送り、頂点シェーダで各モデルを変形
• TRS 行列など
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
擬似インスタンシング
•
1 つのMeshに多数のモデルを格納
• 元の頂点データを繰り返す
• 何番目のモデルか、という情報をどこかに付与 (下記例では uv2)
Mesh (例: Quad)
0,0,0
1,0,0
1,1,0
0,1,0
...
0,0,0
1,0,0
1,1,0
0,1,0
uv
0,0
1,0
1,1
0,1
...
0,0
1,0
1,1
0,1
uv2
0,0
0,0
0,0
0,0
...
16249,0
...
64996
vertices
16249,0
16249,0
16249,0
X:モデルID
indices
0
1
2
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
2
3
0
64997
64998
64998
64999
64996
擬似インスタンシング
•
•
•
必要な batch の数だけ Material も用意
各 Material にインスタンス ID の開始番号を付与
• この数 + モデル ID がインスタンス ID になる
• 例: Cube を 10000 個描きたい場合、10000 / 2708 で 4 つ
必要数を超えたモデルは頂点シェーダで画面に出ないように加工
• 1 個描きたい場合でも格納されているモデルの数分処理される
• vertex.xyz=0.0
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
擬似インスタンシング - インスタンス情報を GPU へ転送
•
何通りかの方法があり、一長一短がある
• テクスチャにプラグインから書き込む
• テクスチャに Mesh 経由で書き込む
• ComputeBuffer
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
擬似インスタンシング - テクスチャにプラグインから書き込む
•
•
•
•
RenderTexture (ARGBFloat) を使用
• Texture2D は Float のフォーマットを使えない
テクスチャにデータを書き込むプラグインを用意
• Texture.GetNativeTexturePtr() でテクスチャオブジェクトを取得
• ネイティブ APIを用いてデータを書き込む
• OpenGL の glTexSubImage2D() など
頂点シェーダからは tex2Dlod() でデータを取得
速いがプラグインを書く必要あり
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
擬似インスタンシング - テクスチャに Mesh 経由で書き込む
•
•
•
•
•
データ転送用シェーダを用意
データ用 RenderTexture をレンダーターゲットに指定
Mesh にインスタンス情報を書き込む
• 注意点:vertices, uv 以外は暗黙に normalize されたりする
Graphics.DrawMeshNow()
遅いがとてもポータブル
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
擬似インスタンシング - ComputeBuffer
•
•
•
•
•
ComputeBuffer は現状 Direct3D11 & PS4 のみ対応
任意のデータ構造の配列を格納可能
データの更新は ComputeBuffer.SetData() を呼ぶだけ
頂点シェーダからは StructuredBuffer<> としてアクセス可能
環境を選ぶが速くて簡単
struct InstanceData
{
float3 position;
float4 rotation;
float3 scale;
};
StructuredBuffer<InstanceData> instance_data;
int instanceid_begin;
v2f vert(appdata_full v)
{
int instance_id = v.texcoord1.x + instanceid_begin;
InstanceData data = instance_data[instance_id];
// ...
} 2014 @ UNITY TECHNOLOGIES
COPYRIGHT
ハードウェアインスタンシング
•
•
•
•
1 回の drawcall で 1 つのモデルを複数描く機能
頂点シェーダの入力にインスタンス別のデータが加わる
• TRS 行列などを渡す
頂点データは 1 モデル分で済む
Direct3D9~, OpenGL 3.1~, OpenGL ES 3.0~, WebGL 2.0~
• OpenGL の glDrawArraysInstanced() など
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
ハードウェアインスタンシング - Graphics.DrawProcedural()
•
•
Unity では Graphics.DrawProcedural() が該当
ただし、色々使いづらい仕様になっている
•
•
Direct3D11 対応環境 / PS4でしか使えない
Mesh をそのまま描くことはできない
• 頂点シェーダの入力は 頂点 ID と インスタンス ID のみ
Unity の render queue に載せることができない
• 呼んだその時点で即座に描かれる (Graphics.DrawMeshNow() と同様)
surface shader が使えない
• 一貫したライティング処理を施すのに工夫が必要
•
•
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
ハードウェアインスタンシング - Graphics.DrawProcedural()
•
対処方法
•
Mesh のデータをそのまま渡すことはできない
• ComputeBuffer に頂点データを格納することで対処可能
• 頂点シェーダで頂点 ID と インスタンス ID から実データを参照
Unity の render queue に載せることができない
• 独自の RenderTexture に描いて結果をマージすることはできる
surface shader が使えない
• deferred であれば G-Buffer さえ生成できればライティングは共通処理を使える
•
•
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
ハードウェアインスタンシング - Graphics.DrawProcedural()
•
Unity5.1 で CommandBuffer.DrawProcedural() が追加
• これにより render queue に載せられるようになる
• G-Buffer の生成、ライティングしないオブジェクトには必要十分
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
ハードウェアインスタンシング - Graphics.DrawProceduralIndirect()
•
•
•
Graphics.DrawProceduralIndirect()
描くインスタンスの数を ComputeBuffer から読み取る
• ComputeShader で描く数を決められる
• = CPU 側の処理を待つ必要がなくなる
GPU Particle などに最適
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
擬似インスタンシング・応用 - DrawProcedural() もどき
•
•
•
DrawProcedural() っぽいことを DrawMesh() で実現
頂点 ID と インスタンス ID のみを格納した Mesh を用意
それらを元に頂点シェーダで実データを取得して描画
Mesh (例: Quad)
X:頂点ID
Y:インスタンスID
vertices
0,0,0
1,0,0
2,0,0
indices
0
2
3
1
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
2
0
3,0,0
...
0,16249,0
...
64996
1,16249,0
64997
64998
2,16249,0
64998
3,16249,0
64999
64996
擬似インスタンシング - まとめ
•
•
Pros
• 今現在の Unity で使える
• Unity の render queue に載せられる
• surface shader を使える
Cons
• 頂点データが肥大化する
• 超過分のモデルも処理される (頂点シェーダ負荷増加)
• (ハードウェアインスタンシングと比べると) drawcall が増える
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update
•
•
•
•
•
万単位のオブジェクトをどう Unity 上で扱い、どう更新するか
1 つ 1 つ GameObject として持つのは非現実的
オブジェクト群を管理するマネージャを GameObject として持つ
オブジェクトのデータは struct の配列として持つ
• 原始的だが速度的には最良の方法
Unity の Collider とのインタラクションなどは独自に実装
• MeshCollider 以外はそれほど難しくない
• 当たった場所に AddForce() することで Rigidbody に力を伝達
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update
•
いくつかの実装アプローチ
• ネイティブコード (=プラグイン化)
• ComputeShader
• C# でがんばる
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - ネイティブコードで実装
•
•
•
•
数値計算の類は C# は苦手
• ネイティブコードで適切に実装すれば 10 倍以上は速くなる
マルチスレッド化
SIMD 化
• データ構造の最適化 (SoA 化)
C/C++ の他、HPC 向け言語も有力な選択肢
• Intel ISPC
• OpenCL (Intel が CPU 実装やコンパイラを用意)
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - ネイティブコードで実装
•
•
描画用データは RenderTextue に格納
• Texture.GetNativeTexturePtr() でテクスチャオブジェクトを取得
• glTexSubImage2D() などを用いてテクスチャにデータを書き込む
Mono の API を用いる方法もある
• C++ から C# の UnityEngine の API を呼ぶ
• ComputeBuffer にデータを移すには現状これしかない
• Mono を介するため低速
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - ネイティブコード実装例
•
•
•
C++ & Intel ISPC
パーティクル同士の相互衝突
• (単純な押し返し処理 & HashGrid による高速化)
30,000 particles @ 60 FPS (Core i7 2.3Ghz x 4 & GeForce 750M)
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - GPGPU / ComputeShader
•
•
•
PC、PS4 / XBoxOne で有力な選択肢
• Unity 5.1 で OpenGL 系の環境も対応
ゲームロジックには影響しないエフェクト類に最適
• パーティクルの更新から描画まで GPU で完結できるため高速
ゲームロジックに影響するものにはやや不向き
• CPU 側にデータを転送するのが大きなロスになる
• GPU のスペックの上限下限の幅の広さも問題
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - GPGPU / ComputeShader
•
ComputeShader ならではのリッチな表現
• G-Buffer を利用したスクリーンスペース当たり判定
• プロシージャルなメッシュの生成 (trail など)
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - ComputeShader 実装例
•
•
左:G-Buffer を利用したスクリーンスペース衝突判定
• Boolean 演算で空けた穴にちゃんと落ちる
右:プロシージャル生成 trail
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - C# でがんばる
•
•
プラットフォームによってはこれ以外選択肢がない
オブジェクトのデータは struct の配列として持つ
// 例
public struct Bullet
{
public Vector3 position;
public Vector3 velocity;
public Quaternion rotation;
public float lifetime;
public int owner_id;
}
int max_bullets = 16384;
Bullet[] bullets = new Bullet[max_bullets];
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - C# でがんばる
•
•
•
できるだけ class を参照しない書き方にする
• class は参照するだけで結構な負担になる
大きなデータのコピーは遅いので極力避ける
ThreadGroup.QueueUserWorkItem() による並列化
• いわゆるタスク並列
• 注意点:Unity の機能の大部分はメインスレッド以外からは触れな
い。メインスレッドで必要なデータを集めておくなどの工夫が必要
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - C# 実装例
•
•
左: 65536 Cube を毎フレーム更新
右: 弾幕
• 約 10000 発 x 50 collider (Sphere x Sphere) の総当り計算
• ThreadGroup による並列化
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - C# 補足
•
数値計算の類は IL2CPP が輝く部分でもある
http://blogs.unity3d.com/2015/01/29/unity-4-6-2-ios-64-bit-support/
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Update - C# 補足
•
最新の .Net でもある程度改善されている
• 並列化機能の拡充
• SIMD 対応 ( RyuJIT )
http://blogs.msdn.com/b/clrcodegeneration/archive/2014/10/31/ryujit-ctp5-getting-closer-to-shipping-and-with-better-simd-support.aspx
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Conclusion
•
•
•
現状大量描画は擬似インスタンシングが有力な選択肢
• 工夫次第で割といろんな問題に対処可能
ハードウェアインスタンシングは、Unity5.1 以降にご期待ください…
• ただし互換性には注意が必要
アップデート処理は可能な限り C# を避けつつ自力で頑張る
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Questions?
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
Resources
•
•
•
BatchRenderer: https://github.com/i-saint/BatchRenderer
• 擬似インスタンシングの実装例
MassParticle: https://github.com/i-saint/MassParticle
• パーティクルエンジンの実装例
• CPU / GPU パーティクル両方
• 描画は BatchRenderer を使用
Intel ISPC: https://ispc.github.io/
• SIMD プログラミング言語
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
End
ありがとうございました!
COPYRIGHT 2014 @ UNITY TECHNOLOGIES
COPYRIGHT 2014 @ UNITY TECHNOLOGIES