Unity を検索

アニメーションインスタンシング – SkinnedMeshRenderer 向けのインスタンシング

2018年4月16日 カテゴリ: テクノロジー | 5 分 で読めます
取り上げているトピック
シェア

デベロッパーは常に CPU と GPU の両方のパフォーマンスを意識するものです。高いパフォーマンスの維持は、シーンが大きく複雑になるにつれて、より難しくなります。キャラクターが多数追加されている場合はなおさらです。私と上海の同僚はお客様のサポートを行う中でこの問題に頻繁に直面したため、キャラクターのインスタンシング時のパフォーマンスを向上させるためのプロジェクトに数週間掛けて取り組みました。その結果生み出された技術をアニメーションインスタンシングと名付けました。

屋外のシーンを GPU インスタンシングで実装するのは一般的です。例えば草や木などです。しかし SkinnedMeshRenderer(キャラクターなど)にはインスタンシングを使用できません。何故かと言うと、スキニングは CPU で計算され、ひとつずつ GPU へサブミットされるからです。基本的には、サブミットによって全てのキャラクターを描画することはできません。シーン内に SkinnedMeshRenderer が多数ある場合、ドローコールの数とアニメーション計算の量が多くなります。

私たちは、CPU の負荷を抑えて Unity での GPU インスタンシングをアニメーションインスタンシングで補完する方法を発見しました。GitHub からコードを入手してください。.これは試験的なカスタムのソリューションで、これまでは Unity Enterprise をお使いの一部のお客様へのサポートの一環として限定的に共有されていたものですが、現段階で、より広くフィードバックの受け入れ準備が整いました。是非プロジェクトのページに直接コメントをお寄せください

目標

現時点で、この試験的プロジェクトは以下の事を目標にしています。

  • SkinnedMeshRenderer のインスタンシング
  • できるだけ多くのアニメーション機能を実装する
  • LOD
  • モバイルプラットフォームへの対応
  • カリング

時間的な制約のため、一部の目標は未達成です。対応しているアニメーション機能は、ルートモーション、アタッチメント、およびアニメーションイベントです(遷移とアニメーションレイヤーには未対応です)。また、アニメーションインスタンシングは OpenGL ES 3.0 以降のモバイルプラットフォームでしか機能しません。

しかしながら、今回の実験的なプロジェクトは、このアプローチのもたらす面白い効果を実証する結果となりました。これがどういうことか、具体的に見て行きましょう。

アニメーションの生成

キャラクター向けのインスタンシングを使用するには、事前にアニメーションを生成する必要があります。ここでは、あるキャラクターのアニメーションをテクスチャ内に生成しました。このテクスチャはアニメーションテクスチャと呼ばれます。このテクスチャは GPU 上でスキニングに使用されます。

このジェネレーターが、処理の対象になっているゲームオブジェクトにアタッチされた Animation コンポーネントからアニメーションを取得します。このジェネレーターはアニメーションイベントも取得します。Mecanim システムからアニメーションインスタンシングへ転送すると便利です。キャラクターに何かをアタッチしたい場合、「Attachment setting(アタッチメントの設定)」でアタッチ先のボーンを指定する必要があります。

アニメーションテクスチャの生成が完了すると、アニメーションインスタンシングスクリプトが実行時にアニメーション情報を読み込みます。(注)アニメーション情報とはアニメーションクリップファイルの事ではありません。

インスタンシング

アニメーションインスタンシングは簡単に適用できます。生成されたゲームオブジェクトにアニメーションインスタンシングスクリプトを追加してみましょう。Bone Per Vertex パラメーターは頂点毎に計算されるボーンの数を制御します。ここで覚えておく必要があるのは、ボーンの数が少なくなると、パフォーマンスが向上する一方、精度が下がるということです。

次に、インスタンシングに対応するためにシェーダーを修正する必要があります。ここでは、シェーダーに以下の行を追加するだけです。これはシェーディングには影響しませんが、スキニングに頂点シェーダーを追加します。

 #include “AnimationInstancingBase.cginc”
 #pragma vertex vert

パフォーマンスの解析

ここでは Mecanim Example Scenes に含まれるデモシーンの若干修正したバージョンを使用して、そのパフォーマンスを iPhone 6 でテストしました。元々の例とインスタンシング版の例の両方のプロファイラ―表示を詳しく確認していきましょう。

CPU

元々のプロジェクトは、およそ 15 FPS で 300 体のキャラクターを生成します。最低 30 FPS 維持できるようにするには、キャラクターの数を 150 体ほどに制限する必要があります。アニメーションインスタンシングを使用したバージョンでは、30 FPS を維持しながら 900 体のキャラクターを生成できます。

お分かりの通り、CPU での計算がプロジェクトの実行速度を遅くします。

インスタンシングのプロジェクトでは、CPU のアニメーション計算(骨格やスキニングなど)を大幅に削減しました。こうすることで、5~6 倍のキャラクターを生成することができます!

このテストシーンでは、環境の描画に約 80 回のドローコールが必要です。このキャラクターはマテリアルを 3 つ持っています。つまり、1 体のキャラクターのレンダリングに 3 回のドローコールが実行されます。

インスタンシング無しの場合、250 体のキャラクターの生成に約 1100 回のドローコールが必要です(3 回 × 250 体のキャラクター + そのシャドウ)。

アニメーションインスタンシングを使用した場合、800 体のキャラクターの生成後、ドローコールの数は約 50 回しか増加しません。インスタンシングの行を確認すると、バッチ化されたドローコールが 4800、バッチが 48(3 × 8 体のキャラクター + 3 × 8 つのシャドウ)あります。これは、バッチ毎に 100 体のキャラクターをサブミットしているからです。

GPU

この方法は GPU の負荷が若干高くなります。スキニングを GPU で実行するためです。キャラクターにシャドウがある場合、シャドウパス中にキャラクターのスキニングを再度行う必要があります。ただし CPU の負荷が削減されるため、全体的なフレームレートは改善されます。CPU の負荷は一般的にゲームの群衆シミュレーションにおける最大の課題です。

メモリ

アニメーションテクスチャを保存するために、追加でメモリが使用されます。アニメーションテクスチャはスキンのマトリックスを保持します。使用するテクスチャ形式は RGBAHalf です。仮に、ボーンを N 個持つ、各ボーン(各マトリックス)が 4 ピクセルのキャラクターがあって、1 つのアニメーションを M 個のキーフレームで生成するとしましょう。この場合、1 つのアニメーションのコストは N × 4 × M × 2 = 8NM バイトになります(訳注:テクスチャ形式に 1 ピクセルに 2 バイトを使う RGBAHalf を想定しているため、テクスチャに使うバイト数を計算する場合はキーフレーム分のピクセル数に 2 を掛けた値が求める値となります)。
ボーンが 50 あるキャラクターがあって 30 のキーフレームを生成する場合、1 つのアニメーションのコストは 50 × 4 × 30 = 6000 ピクセルになります。つまり、1 つの 1024 × 1024 のテクスチャが最大で 174 個のアニメーションを保存できることになります。

まとめ

SkinnedMeshRenderer が多数ある場合の CPU の負荷は、アニメーションインスタンシングによって大幅に削減されることが分かりました。ゾンビなど、類似した敵の群衆に役立つはずです。

この実験的なプロジェクトが、皆様のプロジェクトにおけるパフォーマンス問題の解決の手掛かりとなり、より精巧なシーンのビルドのために役立つことができれば嬉しく思います。今後も遷移やアニメーションレイヤーへの対応をはじめ、更なる改良を模索・検討しています。

是非 GitHub のコードをご確認のうえ、プロジェクトに直接コメントをお寄せください!

2018年4月16日 カテゴリ: テクノロジー | 5 分 で読めます
取り上げているトピック