テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】Visual Effect Graph(VFX Graph)をLWRPで使用する

f:id:tsubaki_t1:20190403232154j:plain

Visual Effect GraphがLWRPに対応したので、その設定方法と、対応方法についてです。

Visual Effect Graph

Visual Effect Graphはノードベースのエフェクト生成ツールです。

パーティクルの後継機という扱いですが、機能が非常に柔軟で単純なエフェクト以外にもマップ的な表現など、様々な応用が効く感じです。動きについては、下の動画がテンション上がります。

www.youtube.com

www.youtube.com

Visual Effect GraphがLWRPに対応

Visual Effect Graphは以前はHDRPのみ対応でしたが、ver 5.8.0 より LWRPにも対応しました 。これでVRやモバイルなどHDRPではしんどい環境でも動作することが期待出来ます。

なお前提としてVisual Effect GraphはCompute Shaderを使用するので、Compute Shaderに対応していない環境では動作しません またAndroidの、特にOpenGLES3系だとComputeShaderが非常に不安定という話をよく聞きます(Vulkanだと割と動くという話も)。使用する場合、ターゲット端末上で動作するのかはちゃんと確認するのが良さそうです。*1

f:id:tsubaki_t1:20190403220649j:plain

ちなみにVisual Effect Graphは、各粒子(パーティクル)の生成タイミングや位置をCompute Shaderで、実際の描画は通常のShaderで行うシステムです。このシステムの面白い所はパーティクルの座標の動きを生成するCompute Shaderをノードから生成出来るという点にあります。

この「粒子の動き」は別のパイプラインでも同じ物が使えるため、描画の部分さえ差し替えてやれば他のパイプラインでも使える事が予想出来ます。つまりLWRPでVisual Effect Graphを使用するには 最終的な描画用シェーダーのテンプレートをLWRPの物に差し替えれば良いという訳です。

(描画部分はoutputノードと一致する感じです)

f:id:tsubaki_t1:20190403223736j:plain

f:id:tsubaki_t1:20190403230726j:plain

LWRP対応の手順

Visual Effect Graphは、描画機能を差し替えるのを割と簡単に実現出来るようになっています(現在は)。なので描画に使用するテンプレートを差し替えてやります。

LWRP用のテンプレートフォルダは、 Packages/visual effect graph/Shaders/RenderPipeline/LWRP にあります。

なので Project Settings > VFXRender Pipeline Settings Path を、 Packages/com.unity.visualeffectgraph/Shaders/RenderPipeline/LWRP に変更してやります。

f:id:tsubaki_t1:20190403231401j:plain

f:id:tsubaki_t1:20190403231424j:plain

あとはVisual Effect Graphのエディターを開き、Compleボタンを押します。

これでLWRP対応のシェーダーが生成されて、LWRPでもVisual Effect Graphが使用出来ます。

f:id:tsubaki_t1:20190403231602p:plain

f:id:tsubaki_t1:20190403234205g:plain
LWRP対応のセットアップ手順

おまけ:Legacy Render Pipelineの対応

LWRPとHDRPの違いは概ね「描画用のシェーダー」…なら、実は通常のれんだーパイプラインでも使えるんじゃね? という発想ですが、実は使えます。

Render Pipeline Settings Path を、 Packages/com.unity.visualeffectgraph/Shaders/RenderPipeline/Legacyとすれば、VFX-Graph ver 5.10現状では使用する事が出来ました。

f:id:tsubaki_t1:20190403231824j:plain

ただし、これは正式対応という訳ではなく「以前にレガシーパイプラインで作ってたから、公開した方が良いと思って公開した。メンテはしない」という立ち位置で、バージョンが上がると使えなくなる可能性がある項目でもあります。Mesh等に対応したい場合は、他のパイプラインのテンプレートを参考に、自分でテンプレートを記述する必要がありそうです。

https://t.co/JNgk1Q1s4P

とは言え、現状Scriptable Render Pipeline系に全面的に移行出来ないというケースもあるので、結構ありがたいと思う自分が居ます。

感想

GPUパーティクル楽しいですね。量を増やしてもCPUが増えない辺り。

関連

blogs.unity3d.com

*1:みんなiPhoneになぁ~れ☆

【Unity】AddressableAssetSystemのAssetReferenceで、一覧表示するアセットを任意の型に制限したい

AddressableでのAssetReferenceで、任意のアセットを指定する方法についてです。

参照するアセットを絞り込んでおきたい

Addressableでアセットの遅延ロードを行いたい時、AssetReferenceでアセットを指定するというアイディアがあります。ただし、この時に登録しているアセットの数が膨大だと非常に面倒な事になります。

特にキャラクター名等、非常によく使うアドレスはテクスチャやオーディオ、その他諸々で複数登録されている事もありえるので、これを一覧から正しく選択しろというのはヒューマンエラーの元です。

f:id:tsubaki_t1:20190402231321j:plain
登録してる中から一つを探し出すのは面倒くさい

これに対応する理にかなったAPIとして [AssetReferenceTypeRestriction( type )]というAttributeがありましたが、何故かコレをver 0.6.6で廃止してくださいやがりましたので、他のアプローチを紹介します。

AssetReferenceTを使用する

解決法としては、AssetReferenceT<T>を継承したクラスを定義します。ジェネリックを使用したクラスはシリアライズ出来ないので、一々AssetReferenceT<T>を継承したクラスを定義する必要があります。

例えば下のような、ゲーム内でHPパラメーターを共有するScriptableObject:GameDataクラスがあったとして…

[CreateAssetMenu]
public class GameData : ScriptableObject
{
    [SerializeField] private int originalHP;
    public int HP { get; set; }

    private void OnEnable() => HP = originalHP; // 起動時にHPを初期化
    public bool IsDead() => HP < 0; // HPが0以下だったら死亡判定
    public void Damage(int value) => HP -= value; // ダメージを受けたらHPを減らす
}

f:id:tsubaki_t1:20190402232044j:plain
GameDataインスタンスはアセットとして複数のPrefabやSceneで共有可能にしてある状態

GameDataのみを使用可能なAssetReferenceTを用意します。

[System.Serializable] // Serializable属性が必ず必要
public class AssetReferenceGameData : AssetReferenceT<GameData> { }

あとは AssetReferenceの代わりにAssetReferenceGameData をAddressableの呼び出し元コードに設定します。

f:id:tsubaki_t1:20190402232525j:plain
指定したアセットしか表示されない

public class PlayerSpawn : MonoBehaviour
{
    // AssetReferenceではなくAssetReferenceGameData
    [SerializeField] AssetReferenceGameData gameData; 

    private void Awake()
    {
        enabled = false;
        gameData.LoadAsset().Completed += (data) =>  enabled = true;
    }

    private void OnDestroy()
    {
        gameData.ReleaseAsset();
    }

    private void Update()
    {
        // 取得済みのアセットを使用するが、こちらはキャストが必要
        ((GameData)gameData.Asset).Damage(1); 
    }
}

補足

ちなみにAssetReferenceTで指定出来る型は、UnityEngine.Objectのみみたいです。正確にはUnityEngine.Objectのアセットのみっぽく、Componentは指定出来ませんでした。ここにInterfaceが指定出来れば、色々な面で非常に楽になるんですが… ふっきゅんきゅんきゅん!

ついでにAssetReferenceLabelRestriction属性も廃止になり、ラベルを取得するAssetReferenceUILabelRestrictionクラスになりました。こちらは悪くないです。

【Unity】AddressableAssetSystemで「PackedPlayMode」と「FastもしくはVirtual Mode」で挙動が違う場合

Addressable Asset Systemで、とりあえず Fast / Virtual Mode ではちゃんと動くのに Packed Play Mode では上手く動かないという場合の、経験則的な対策についてです。 f:id:tsubaki_t1:20190401223923j:plain

Packed Play Mode と Virtual mode / Fast Modeの違い

Packed Play Mode と Virtual Mode や Fast Mode、この2つは非常に大きく異なります。

用途としてVirtualとFastはコンテンツの開発にフォーカスされており、動作の確認と修正を速やかに繰り返しやすい設計になっています。 VirtualはFastの高速性に加えて、コンテンツのロード・アンロードの動きを追跡しやすい設定です。アセットやAssetBundle開放のタイミングを把握することでメモリ管理に役立てようという発想です。FastやVirtualはAssetDatabaseから直接取得するタイプのロードを採用しており、AssetBundleをビルドし展開して使用する Packed とはロード方式が異なります。

一方、Packedは実際の動作と完全に同じ動きを行うことが期待されており、エディターでと言うよりは、実機での動作を確認することを期待するものです。実機で発生した不具合をエディターでも確認出来たら良いな程度に考えるのが良さそうです。

f:id:tsubaki_t1:20190401222519j:plain

この2つ、ロード方式の違いから、Fast/VirtaulをPackedに上げると予想外の動きを起こす事があります。正確には 正しくない設定でも何となく動いてしまっていたものが、設定をちゃんとしないと動かなくなる なのですが、サクッと試すと結構混乱するので色々とメモします。

まぁ、Addressableさんのエラーメッセージが不親切の上に追いにくいというのもあるんですが…

ゲームの再生が出来ない

Packedに変更すると、ゲームの再生が出来なくなります。これはコンテンツ(AssetBundle)を作成していないためです。下のエラーメッセージを解決してやればビルドが可能になります。

Player content must be built before entering play mode with packed data. This can be done from the Addressable Assets window in the Build->Build Player Content menu command.

f:id:tsubaki_t1:20190401224021g:plain

要するにBuild > Build Player Contentを押せば良いです。

f:id:tsubaki_t1:20190401224121j:plain

これはBuild for content updateでコンテンツの更新を生成した場合も同様になります。

コンテンツや設定の更新が反映されない

コンテンツや設定の更新が反映されるのは、Build Player Contentでビルドした後からです。これは Addressable Asset Settings等での設定も含まれます つまり、RemoteLoad等の参照先を切り替えた場合、Build Player Contentの更新が必要です。

Catalog等の設定については案外StreamingAssets/aa/settings.jsonを書き換えれば良いかもしれませんが、グループやプロバイダーについては再ビルドが必要です。

まぁ再ビルドといってもキャッシュが結構効くので、ファイル数が膨大でなければ(それこそ1Asset1AssetBundleとかしてなければ)割とサクっと終わる印象です。

UnknownResourceProviderException や Unable to read header from archive file:が出る

これはBuild Asset Group Schemaのプロバイダーの設定が間違っています。

このプロバイダーの設定は、現状1択で、異なる値を設定するとエラーが出ます。将来的にAddressableがAssetBundle以外のバイナリフォーマットに対応すれば話は代わりますが、現状のBuild ScriptはPack Mode1択なので、この2つが変化することはありません。

Unable to read header from archive file:が出た場合はBundled Asset Provider Type(AssetBundleをどうやってロードするか)が間違っています。Bundled Asset Providerを設定します。

UnknownResourceProviderExceptionが出た場合はAssetBundle Provider Type(どうやってAssetBundleの中身を取得するか)が間違っています。Asset Bundle Providerを設定します。

f:id:tsubaki_t1:20190401225500j:plain

なお変更が反映されるのは上に書いたとおり、Content Build Playerでビルドした後です。

Exception thrown in DynamicInvokeが出る

RemoteLoadPathを使用している状態で、ホスティングサービスやURL等を特に指定してない時に起こります。

Exception thrown in DynamicInvoke: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.UriFormatException: Invalid URI: Invalid port specified.

ホスティングサービスを設定するなり、任意のサーバー上にファイルを設定すれば使えるようになります。

Schemaで確認したとき、RemoteLoadPathのアドレスがちゃんとなっていればOKです。

f:id:tsubaki_t1:20190401230106j:plain

Exception: RemoteAssetBundleProvider

RemoteLoadPathを使用している状態で、ホスティングサービスが動作していない、もしくはアクセス出来ない(アドレスが間違ってる等)の状態です。

特にホスティングサービスを使用した時、初期設定はRemoteLoadPathの設定が明確に間違っているので、ちゃんと設定してやる必要があります。例えば http://[PrivateIpAddress_3]:[HostingServicePort]等。 少なくともLoadPathには[BuildTarget]は使用しません。

tsubakit1.hateblo.jp

404が出る

Hosting Serviceがちゃんと起動してないです。ホスティングサービスをUnityエディター起動後に初めて起動した時に出ます。

ホスティングサービスを再起動します。ポートが塞がってる訳でもないんですが、何ででしょう

複数のPrefabでScriptableObjectを共有してるとき、ScriptableObjectの変更しても反映されない

ScriptableObject等で値を共有している場合、共有用のScriptableObjectをAddressableに登録しないと、オブジェクトがPrefab毎に複製されて値が共有出来ないという事があります。例えばアドベンチャーサンプルゲームではシーン間の設定をSOで管理しているので起こります。

SO経由のデータ共有(いっそSO自体をマネージャーにする)はSingletonを使わず全体で動作にアクセス出来て楽で良いのですが、こういった事もあります。

tsubakit1.hateblo.jp

これはScriptableObjectにアドレスを割り振り、複数のAssetBundleで共通のインスタンスを使わせれば良いです。Analyzeでも検出されました。

またResourcesとAddressableでScriptableObjectの共有は出来ません(どちらかがインスタンスの参照ではなく明示的な設定が必要になります)

複数のSceneでScriptableObjectを共有してるとき、ScriptableObjectを変更しても反映されない

上の設定に追加で、Sceneで使用する場合に注意が必要なのが、ゲーム開始時に開いているSceneが参照するリソースはAssetDatabaseから取得しているという点です。

これは基本的にAddressableに登録しているシーンは一旦Addressableシーンから起動してもらう事で回避が可能です。Packedは動作確認用なので、最初から確認してくださいということで。

感想

URLなど最初から明確に間違ってるのがありますが、ちゃんと設定すれば割とちゃんと動きます。逆に、ちゃんと設定してない箇所があると、ちゃんと動いてくれないです。

今回は基本的にPlaymodeがFastやVirtual Modeのときにちゃんと動くことが前提です。動かない場合は、多分アドレスが間違っているとかです。

関連

www.slideshare.net

後半に出てきたScriptableObjectによるデータ共有。テストとかやりやすい

tsubakit1.hateblo.jp

【Unity】AddressableAssetSystemでSpriteAtlasを使用する

Addressableを使用した場合も、SpriteAtlasの利用手順はそれ程代わりません。

AddressableでSpriteAtlasを使用したスプライトを利用する

Addressableは所属するグループやAssetBundleをそれ程意識しなくても利用できるシステムです。ただし暗黙的な参照によるアセットの重複はやはり存在するので、特に何も気にせずアセットを利用すると、アセットが重複する事があります。

例えば下の様に複数のAssetBundleから一つのSpriteAtlasにパックしたアセットへアクセスする場合、複数のAssetBundleにパックしたテクスチャが格納することになり、メモリ効率・データサイズ・描画性能的にペナルティがあります。

f:id:tsubaki_t1:20190331225746j:plain

実際、AssetBundleの中身を確認してみると、各AnimationClipの他にパックされたテクスチャ、そして全てのSpriteが含まれてしまっていることが確認出来ます。

f:id:tsubaki_t1:20190331225524j:plain

対策

Analyzeを使う

Addressableは有り難い事に、重複するアセット*1を個別のAssetBundleへ退避する機能があります。コレを使用すると、スプライトの重複を回避してくれます。

  1. Analyzeボタンを押す
  2. Run Testsボタンを押す
    重複するアセット一覧が表示される。
  3. Fix Allボタンを押す
    新しいグループが出力される

f:id:tsubaki_t1:20190331230345j:plain

あとはBuild Content Playerを実行すると、SpriteAtlasの重複部が一つのAssetBundleに格納され、他のAssetBundleからSpriteやパックしたAssetBundleの情報が剥がされます。

また各pngファイルを指定しているのでパック前のTextureも含まれる事を懸念していましたが、以前に確認したとおり現状でも問題なくパックしたテクスチャのみが含まれました。

f:id:tsubaki_t1:20190331230830j:plain

手動でスプライトを配置を使う場合…

一応補足しておくと、Analyze経由でスプライトを配置しなくても「SpriteAtlasに含めたスプライトを全て同じグループ(同じAssetBundle)に所属する」だけでOKです。これはAddressableにフォルダを突っ込む(フォルダ以下のアセットを全登録する)という形でも達成出来ます。

f:id:tsubaki_t1:20190331231824j:plain

なんか上手くSpriteAtlasが更新されない場合…

SpriteAtlas内のSprite群を足したり引いたり色々していると、Addressableでビルドした時にSpriteAtlasが更新されていないという現象を見かけました。これはAddressableの Build > Clean を実行すれば回避出来ました。

感想

割と素直に使えました。

Analyzeが無かった時は割とどうしようか感はありましたが、Analyzeでルールに従って重複を排除してくれるのは楽でいいです。このルールは現状一つですが、今後自作で追加したり色々出来るそうなので、そうすると割と良いんじゃないかなと。

関連

今回主に使用した機能

tsubakit1.hateblo.jp

問題の本質

tsubakit1.hateblo.jp

一応、今回のケースでもInclude in buildを外すアプローチは使えます。無駄なデータと余計な処理が追加されるのも同様です。

tsubakit1.hateblo.jp

*1:正確には少し異なるルールのように見えます。単純に重複したというより、大本が重複している物のみを選んで抽出しているように見えます