UnityでAnimationInstancingを実現してみよう(2)
Github (Unity 2017.1.3f1)
きっかけ
最初Unityでオープンワールド的な何かを作ってみよう!と思って色々試していた最中、
アニメーションが付いているSkinnedMeshRendererが増えるほど、非常に重くなっていくのをみてユニティでのアニメーションとGPUとの関係を調べた結果、
**GPU Instancing用のシェーダーはアニメーションが付いているSkinnedMeshRendererはサポートしていない。**というのをみて、GPUでアニメーションインスタンシングを実現してみようと思いました。
見えているモデルは少ないけど、アニメーション+NavMeshでCPUの負荷が相当かかってしまう。(パソコンがあまりに良くないのもありますが)
UnityのGPU Instancing
ユニティ5.4から公式的にサポートし始めています。
前提としては、
そもそもGPU InstancingをサポートしているGraphicLibraryじゃないといけない。
使い方としては、簡単にInstance用シェーダーを追加しマテリアルに適用するだけでOKです。
ですが、アニメーションをサポートしていない限り、効用性としては限定的なところ(背景、アニメーションがないアイテムオブジェクト等)にしか活用できないですね。
UnityのGPU Skinning
基本的にユニティのSkinningはCPU上で行うようになってますが、PlayerSettingsのOther SettingsのRendering項目でGPU Skinningオプションを設定するのができます。
適用したい方法
http://http.developer.nvidia.com/GPUGems3/gpugems3_ch02.html
GPU Gems3の Chapter2. Animated Crowd Renderingを使おうとしました。
アニメーションのボーンマトリクスをテクスチャーに保存し、Instanceシェーダーの頂点情報としてボーンのWeightとボーンインデクスを渡せばOKという感じですね。
適用可能なのかの検証作業
1. Global Textureの設定は可能なのか
まずはマテリアル別にアニメーションテクスチャーを渡すのはあまりにやりたくないので、
シェーダー共通のテクスチャーの指定ができるのかを調べてみました。
よし。問題解決。
2. 頂点ごとにカスタムな情報を渡すのが可能なのか
UnityではUNITY_DEFINE_INSTANCED_PROPというのを使ってInstanceごとに情報を渡すのが可能ですが、今回は頂点ごとに情報を渡したい感じだったので、
Vertexの情報として必要なのはボーンのインデクスとウェイトのペア。ユニティは一つの頂点で最大4個のボーンをBlendingできるので、 TexCoord2(float4)とTexCoord3(float4)を使えば大丈夫そうです。
足りなさそうだったら QualitySettingsのBlendWeightsを減らせばよさそうですね。
3. ボーン情報の獲得
UnityのMeshからボーンの情報を獲得するのはこういう感じできれいにできます。
for (int i = 0; i < mesh.vertexCount; i++)
{
var position = mesh.vertices[i];
var weights = mesh.boneWeights[i];
var boneIndices = new int[] { weights.boneIndex0, weights.boneIndex1, weights.boneIndex2, weights.boneIndex3 };
var boneWeights = new float[] { weights.weight0, weights.weight1, weights.weight2, weights.weight3 };
}
問題点
問題点としては、既存のユニティのアニメーションコンポネントを使えなくなるのがあります。
結論
Animation InstancingをユニティのGPU Instancingにのせて実現するのは可能だと思われます。
実際ググってみたら、既にUnity上でAnimation Instancingを実現した動画が結構あったので、次回は二つのパートに分けて、アニメーションテクスチャーを作るところとシェーダーで適用するところを順番で書いていきたいと思います。