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
© Copyright 2024