VFX Graph の可能性を探る!PointCache動的生成、VAT拡張
こちらは【unityプロ技②】 Advent Calendar 2019 24日の記事になります。
本記事では2つ VFX Graph で使えそうな拡張技術について検証してみました。VFX Graph 自体の使い方にはあまり触れていないのでご了承ください。
リポジトリはこちら
検証環境は次のとおりです
Windows10
Unity 2019.3.0f1
High Definition RP 7.1.5
Visual Effect Graph 7.1.5
VFXGraphとは
正しくは Visual Effect Graph ですが、よく略して VFX Graph と表記されます。ComputeShaderを生成するビジュアルプログラミング環境を提供するUnity公式のPackageです。特にGPUパーティクルに特化している印象です。
いままでComputeShaderを使うには
1.ComputeShader自体のコード
2.ComputeShaderの計算結果を可視化するShaderコード
3.それらを適切にコントロールするC#側のコード
が必要で初学者が正しく動作させるまでのハードルが高いものでした。
これらのうち3.を自動化し1.2.を制限付きでビジュアルプログラミング化することで敷居を下げ、また、トライアンドエラーしやすくなるツールが VFX Graph です。
用語
本記事を読むうえで必要な VFX Graph 用語を押さえておきます。
Attribute - パーティクルごとの固有値。position,sizeなど。
Map - データバッファとして使うテクスチャ
Contexts - 縦方向につながる特殊なノード。Spawn,Initialize,Update,Outputの4種があります。
Block - Contexts内で処理を記述するノード
Operator - Contexts外で処理を記述するノード。最終的にContextsかBlockのスロットに繋げて値を流し込む
Point Cache
VFX Graph の機能の中でもおもしろいのがPointCacheで大量の点の情報をComputeShaderに渡すためのデータになっています。点の位置のほかに色などの情報を含めることも可能です。シンプルなフォーマットで外部ツールなどでも生成しやすく、 VFX Graph 自体にもメッシュからPointCacheを生成できるツールが用意されています。
pCacheTool
(Window→Visual Effect→Utilities→Point Cache Bake Tool)
Unity公式デモのMorphFace。PointCacheはこのように計算では求めにくい形状を扱うのに向いています。
PointCacheを実際に使ってみると VFX Graph 上では点の数と複数のテクスチャのセットになっていることがわかります。
コードを見てみるとScriptedImporterでロード時にテクスチャに変換しているのがわかります。
*ソースの場所はこちら
Packages/Visual Effect Graph/Editor/Utilities/pCache/Impoter/PointCacheImpoter.cs
ということは、このテクスチャを動的に生成すれば実行時にPointCacheを生成したような挙動ができそうです。やってみましょう!
AttributeMapのフォーマット
まずはテクスチャの形式を調べます。
VFXGraphにはブロックを選択するとInspectorにコード片が表示される便利な機能があります。
設定でON/OFFできるのでもし表示されない場合はEdit→Preferences...→Visual Effects→Show Additional Debug infoにチェックを入れてください。
Set Point from Mapブロックをみるとテクスチャの値をpositionという変数に代入していることがわかります。
float3 value = (float3)attributeMap.t.Load(int3(x, y, 0));
value = (value + valueBias) * valueScale;
position = value;
attributeMap.tがテクスチャですね。ブロック上で指定できるvalueBias、valueScaleの具体的な処理もわかります。特に凝ったことせずにテクスチャに色として位置の値をいれておけばよさそうです。
ちなみに VFX Graph のファイルをProjectViewで開くと出力されたComputeShaderやShaderが直接見ることができます。
自動生成のコードなので人間が読むには辛い部分もありますが頑張れば処理全体を追うことができます。
メッシュから動的にAttributeMapを生成する
Mesh.verticesで頂点の位置を取得できるのでこれをColorにしてテクスチャに入れていきます。
しかしこれでは頂点の位置しか入ってないのでだいぶスカスカです。ポリゴンの面上を埋め尽くすように点を増やしてみます。
Mesh.trianglesでポリゴンごとに処理します。ポリゴンの面積に応じて生成する点の数を決め、ポリゴン上でランダムな点を取っていきます。今回はverticesの位置と同様にuvやnormalsも取得しAttributeMapとして使っています。
ソースコードはこちら
VFX Graph で自作AttributeMapを使う
VFX Graph に外部から入力する作法としてBlackboardにプロパティとして登録しExposedのチェックを入れておきます。
C#側でプロパティを渡すコードはこんな感じです。
public VisualEffect effect;
effect.SetTexture(”PostionMap", positionTexture);
こうして渡されたプロパティを VFX Graph 側でAttributeMapとして使います。といってもここからはPointCacheと同じでSet * from MapブロックでAttributeMapから値を取り出て使っていくだけです。
ところで今回の実験では3Dのモデルがパーティクルに分解される表現を目指しました。なんとか「(UVMapテクスチャとして渡す)UV値とモデルのテクスチャからColorをゲットしてSetColorする」必要がありましたが標準的な方法はなさそうでした。悩んだ結果、
1.一度UVMapからUV値をPositionに書き込み
2.そのPoistionからモデルテクスチャを参照しSetColor
3.本来のPoistionをPositionMapから書き込む
という方法にしました。
UVMapとModelMainTexからColorを取得するブロックの流れ
現状Mapを参照できる機能はブロックにしかありませんがOperatorノードにもあるとこういったときに便利そうです。
Operatorノードでテクスチャ参照する方法としてSample Texture2Dがありますが、こちらはテクスチャ本来の用途である色の集合として扱うため隣のピクセルと補間がかかったりMipMapを見に行ったりと無加工のデータを取り出すには余計な処理が入る余地があり向いていません。ComputeShader上でも呼ばれるメソッド自体が異なります(ちょっと試したけどうまくいきませんでした)。
ここまでできればあとは生成したAttributeMapを用いて遊んでいくのみです。
最終的にクリックすると分解するKyleくんができました。
クリック時にC#側で下記のような処理を動的に行っています。
クリック対象のモデルからメッシュを取得
メッシュからAttributeMapも生成
Visual Effectの入ったGameObjectを生成
Visual EffectにAttributeMapをセット
対象のモデルを非活性化
パーティクルが戻ってくる頃に対象モデル活性化
ソースはこのあたりです。
VAT(VertexAnimationTexture)対応
次に VFX Graph でVATを使えないか試してみました。
リアルタイム映像表現でComputeShaderと合わせてよく使う技術にVAT(VertexAnimationTexture)というものがあります。アニメーションの毎フレーム分の頂点情報をテクスチャに格納して頂点シェーダーで復元するテクニックです。ちょうどPointCacheは頂点情報をテクスチャ化してComputeShader側で読む実装になっていましたがこれをアニメーションに応用したような感じです。
大量のオブジェクトをアニメーション付きで同時に表示することができるので動物の群れなどの表現に効果的です。逆にMecanimのような複雑なアニメーション処理は苦手で単純なループアニメーションを再生するくらいがちょうどよいと思います。
というわけで、ComputeShader使うならVATも組み合わせたい!けど今のところそのような機能は VFX Graph には見当たらない。ならばなんとかできないかと、試してみました。
⚠ 注意事項
以下の方法はかなりDirtyなハックになります。 VFX Graph が公開することを意図していない部分にアクセスするため将来性や安定動作などに問題があり得ます。あくまで実証実験として参考にしてください。
VFX Graph にOutputノードを追加する
VATを使うには専用のシェーダーを用意する必要があります。 VFX Graph ではシェーダーは隠蔽されているのでまずは「自作のシェーダーを呼べる自作ノードの作成」から始めます。参考にするのはMeshを表示するOutput Particle Meshがよいでしょう。
Output Particle Meshノード
調べたところ対応するソースコードは以下のものでした。
Packages/Visual Effect Graph/Editor/Models/Contexts/Implementations/VFXMeshOutput.cs
これを真似ていきたかったのですがinternalクラスを使っていたりとそもそも正攻法では自作ノードを追加できなそうでした。
というわけでイレギュラーな技でいきます。
おもむろに以下のソースを見てみると
Packages/Visual Effect Graph/Editor/PackageInfo.cs
[assembly: InternalsVisibleTo("Unity.VisualEffectGraph.EditorTests")]
という記述があります。
InternalsVisibleToAttributeはいわば「指定したアセンブリからならinternal見えてもいいよ」宣言です。おそらくテスト用に指定してあるものと思われます。これを利用しましょう。
手元のフォルダに.asmdefを作成しInspectorからNameをUnity.VisualEffectGraph.EditorTestsにします。
必要なアセンブリの参照設定もしておきます。
ShaderGraph連携もできるようになりつつあるため依存していました。
これでこの.asmdef以下のフォルダで自作ノードを作る環境が整いました。VFXMeshOutput.csをコピーして自作のOutputノードを作ります。今回のプロジェクトではVFXVATOutput.csというファイルにしました。
できあがったソースコードはこちらです。
以下の点を書き換えています。
nameプロパティ
VFXGraphエディタ上で表示される名前です。今回は”Output Particle VAT”としました
codeGeneratorTemplateプロパティ
自動生成するシェーダーのテンプレートファイルのパスです。
OptionalInputPropertiesクラス
メンバーがVFXGraphエディタ上でパラメータとして表示されるようです。
vertexAnimTexture、animTimeを追加しています。
CollectGPUExpressions()
mainTextureを見よう見まねでOptionalInputPropertiesに追加したメンバーの処理を追記しています。
とりあえずこれでVFXGraph上で表示されるOutputノードができました!
*VFX GraphのShader内では_Timeが使えなかったのでAnimTimeで時間を渡しています
Main Texture、Mesh、あらかじめ作っておいたVATテクスチャをそれぞれ対応したスロットにセットしています。リポジトリに簡単なVATテクスチャ生成ツールも入っているのでよかったらご覧ください。
自作シェーダを呼び出す
VFX Graph はシェーダーに特定の書式を付け足した.templateファイルをもとにシェーダーを生成しています。コンテキストでは先ほどのcodeGeneratorTemplateプロパティで指定しているテンプレートファイルを参照しています。これらのファイルも VFX Graph パッケージからコピーして改造していきます。
厄介なのは各テンプレートファイルから別のテンプレートファイルを参照できる構文があり、参照先も含めて対応する必要があります。
VFXInclude VFX Graphパッケージフォルダからパスで指定した参照
VFXIncluceRP SRPパッケージフォルダからパスで指定した参照
結局、対象となるテンプレートファイルはcodeGeneratorTemplateプロパティの
Packages/High Definition RP/Editor/VFXGraph/Shaders/Templates/VFXParticleMeshs.template
と、そこから参照されている下記のフォルダ内の.templateになりました。
Packages/Visual Effect Graph/Shaders/ParticleMeshs
各テンプレートファイルでは最終的に頂点シェーダーを参照するVFXInclude文を以下のように書き換えてコピー側を見るようにしています。
${VFXInclude("Shaders/VFXParticleHeader.template")
↓
${VFXInclude("../../Assets/VATExtension/Templates/Shaders/VFXParticleHeader.template")
あとは頂点シェーダーが記述されているPass.templateを改造してVAT対応のシェーダーにします。
頂点シェーダの引数にSV_VeterxIDな値を追加し、
VFX_VARYING_PS_INPUTS vert(vs_input i, uint vId : SV_VertexID)
あらかじめ VFX Graph エディタ側でセットしているvertexAnimationTextureを参照してinputVertexPositionを書き換えてます。
// VAT
float timeStep = animTime * 60.0 + index;
uint width, height;
vertexAnimTexture.GetDimensions(width, height);
inputVertexPosition = vertexAnimTexture[uint2(vId, timeStep % height)];
これでVATが使えるOutput Contexts ノードが完成しました。
今回はこれを使って大量のKyleくんを出して集団アスレチックにして遊んでみました。
動く床と球は VFX Property Binder を使ってそれぞれCollide With AABox、Collide With Sphereとして処理しています。
まとめ
VFX Graph でどんなことができると面白いかなーと考え、動的なPointCacheとVATの対応について実験してみました。VATはとても相性がよいので VFX Graph 内の機能として実装されるとよさそうな感触でした。
ビジュアルプログラミングは抽象化や汎用性を持たせるのは苦手ですが、ロジックを含めたトライアンドエラーが気軽に試せるので、絵を出して見てみないとわからない映像表現をする際に非常に強力なツールだと思います。今後の VFX Graph の発展が楽しみですね。
使用アセット
Space Robot Kyle
https://assetstore.unity.com/packages/3d/characters/robots/space-robot-kyle-4696
Mixiamo
https://www.mixamo.com/
もしお役に立ちましたらスキ(♡マーク)をお願いします!!! noteアカウントがなくても大丈夫です。サポートもお待ちしています!