【Unity】LWRPでポストプロセスを使用しつつUIの手前にパーティクルを表示する

PCを新しくしてからの初更新です。
環境:Unity 2019.2f8、LightWeight RP 6.9.1
LWRPはカメラのスタックができない
いろいろと便利なLWRPですが、一つだけよく言われる致命的な問題があります。 カメラを複数使用して描画する事ができない という点です。 RenderTexture に描画結果を出力して後でまとめるといった事は可能なのですが、今まで使ってきた 複数のカメラを使って描画する事が出来ないのは、非常にもどかしいです。
特に問題になりやすいのはUIとパーティクルの描画です。 よくある演出として UIの手前にエフェクトを表示する があります。これは大抵の場合「ゲームを描画するカメラ」「UIだけを表示するカメラ」の二つに分けて描画し、「UIだけを表示するカメラ」にパーティクルを描画させることで実現していました。 ただしLWRPになりカメラのスタックが出来なくなったので、専用カメラが使用できません。よって異なる方法を考える必要があります。
難しいのが、まず ScreenSpaceOverlay は使えません。UIの手前にパーティクルを描画出来ない為です。
また ScreenSpace-Camera でも単純には上手くいきません。単純に処理すればUIがポストプロセスの影響を受けるので、特にカラーコレクションを使用すると盛大に色合いが変わってしまいます。またBloomやDOFの影響を受けるのはひどいです。同様にPlaneDistanceの設置でUIがステージにめり込む事もあり得ます。この設定をいくら小さくしても、手前で爆発するなど何らかの理由でUIよりステージオブジェクトが手前に描画される事があるのは問題でしょう。


PostProcessingの後にUIとパーティクルを描画する
一番手っ取り早いアプローチは Custom Forward Render を使用して、UIとパーティクルを描画する事です。 このアプローチではUIをポストプロセスの影響を受けないようにしつつパーティクルをUIの手前に描画出来ます。また Screen Space Camera を使いつつもステージやキャラクターの後ろにUIが表示されるといった事も回避できます。
この手順のアイディアは非常に単純で 任意のレイヤーに所属するオブジェクトを、ポストプロセスの後に描画しよう というものです。
実際の手順はこんな感じです。
まず最初にUIは ScreenSpace-Camera を使用する必要があります。 CanvasのRender ModeをScreen space-Cameraに設定します。またCanvas GameObjectのレイヤーはUIに設定します。パーティクルのGameObjectもUIに設定します。
パーティクルをUIの手前に描画したいので、Order in Layerを使用してUIの手前にパーティクルを描画するように設定します。 UIの場合はCanvasに、パーティクルの場合はRendererにあります。


レンダラー側の設定を行います。まずは Create > Rendering > ***Pipeline > Forward Renderer でCustom Forward Rendererアセットを作ります。その後、Lightweight Renderer Pipeline AssetのRender TypeをCustomに設定し、Custom Forward Rendererアセットを登録します。


次にCustom Forward Rendererの設定を行います。ここは少し手順が多いので、箇条書きします。
Default Layer MaskからUIを外す。Render Featuresの+を押す。追加するパスはRendere Objectを選択NameをUIに変更(任意)EventをAfter Renderingに変更Filters > QueueをTransparentに変更Filters > Layer MaskでUIを選択Override > Depthにチェックを入れ、Depth TestをDisabledに変更

これでUIの手前にパーティクルを描画出来ました。
UIの間にパーティクルを挟む
今回の手順では描画順は完全にOrder In Layer順になっています。なのでパーティクルの所属するOrder In LayerをCanvasの間に挟んでやれば、UIとUIの間にパーティクルを挟む事も可能です。

注意点
これはあくまでもパーティクルがTransparentを使用している前提の使い方なので成立しています(つまりDepth Testを使用していない事が重要)。もしUIの手前に複数のSubMeshを持つオブジェクトを描画するといった場合はRenderTextureをオフスクリーンで描画する方が理にかなっていると言えるかもしれません。
今回の手順はURPでも使用できました。HDRPは不明です。
ScreenSpace-Cameraを使用する関係上、編集する場合にはPrefabモードが便利です。ただし、その場合ScreenSpace-Overlayになるので(PrefabからScene上のカメラを参照出来ない為)描画順回りは少し混乱するかもしれません。
カメラのスタックはロードマップには載っているみたいです。現状は未実装なので、このトリックが必要という感じで。
【Unity】Particle SystemにLODを適応してみる
何となく思いついて、LODをパーティクルに適応したところ、出来てしまいました。何の役に立つのかと思ってみましたが、これが思いの外有効そうです。
例えば複数の演出で成り立っているパーティクルの場合、距離に応じて演出を追加していくのもアリかもしれません。
下の場合、LOD0は7個のパーティクルから成り立っていますが、LOD1は4個、LOD2は2個のパーティクルまで削減しています。

他のもいくつか使いみちがありそうですが、同時に機能しないどころかパフォーマンスを悪化させるケースもあります。
今回はソレについて紹介します。
目次
- LODとパーティクル
- Particle SystemにLODを設定してみる
- で、パフォーマンスは?
- 描画負荷は削減される
- パーティクル自体のパフォーマンスはprocedural modeなら効率化
- LODよりCullingGroupの方が良いかもしれない
- 感想
- 関連
【Unity】パーティクルをGPU Instancingで描画してみる & 対応シェーダーを自作してみる

Unity 2018.1では、Instancing関係の機能が大幅に使い勝手良くなりました。
特にメッシュパーティクルの描画はCPUバッチングではなくGPU Instancingで行えるようになり、幾つかのケースでパフォーマンスが大幅に向上する事があります。
Unity 2018.1 でパーティクルに追加されたenable GPU Instancingを有効にしてみるテスト。無効(今まで通り)だと6~10FPSくらいしか出ないパーティクルの量でも、60FPS余裕で維持できた pic.twitter.com/wgRQ3Q2afu
— 椿 (@tsubaki_t1) 2018年1月10日
例えば、パーティクルを描画するとレンダースレッドのGfx.ProcessCommandsが凄い事になっているような場合、この項目を見直すと劇的に改善するかもしれません。*1


他にもUnity 2018ではInstancingに多くの改良点がありますが、今回はパーティクルの内容に絞って紹介します。
目次
- パーティクルとバッチング
- GPU Instancingとバッチング
- パーティクルのGPUInstancingを使用してみる
- 自作のシェーダーを使用する
- 実際のコード
- 関連
*1:この項目を計測する時はvsynkは無効にする
【Unity】Timelineで敵の”出現タイミング”や”動き”を制御してみる
今回はUnity Advent Calendar 2017 のネタで、「Timelineがカットシーンを作るだけのツールではない事を教えてやる!という妄想を書きなぐる」内容です。
要するに、TImelineでゲームの進行を管理してみようぜ!というお話
Timelineを動画作成以外に使用する
Timelineは所謂カットシーンエディタとして紹介されることが多いです。例えばAdamやUltimate Bowlのような高級デモ。もしくはドアや宝箱の解放といった特定ギミックアクションで。

ただ、最近のUniteの講演動画を見てふと思いました。
別に、TImelineをカットシーン以外に使っても構わんだろう?
Timelineは時間を制御する
Timelineは時間を制御するシステムで、Playableは本質的には時間を与えたら結果を返してくれる機能です。
そのため、時間がある程度固定的な動きであればTimeline上から動作を制御することも可能なんじゃないか?と考えたわけです。

思いついたので早速試作。
ではどんなゲームが良いか・・・となる訳ですが、ゲーセンにあるタイムクライシス的なゲームを考えましたが、今回はサクッと単純な、本当に単純なシューティングを作って見ました。
ゲームを再生せず動きをプレビュー
実際に作ってみたところ、敵の出現タイミングや動き、ちゃんと作り込めば弾幕の生成などの動きも含めてゲームを再生せずTimelineのヘッドを動かすだけでプレビューができました。
。
本当はPlayableをガンガン拡張したものを紹介する予定でしたが、プロジェクトを日本に置きっぱなしにしてしまったので、先ほどサクッとプロジェクトを作成しました。
作ってみる
サクサクっと作ってみます。
- 敵の出現と動き、および量産
- 弾の発射
- 倒さないと次に進めない敵
敵の移動はAnimation
まず敵の出現と移動です。
これは以前Tweenを使っていたものを自作しましたが、データが自宅なので今はAnimationTrackを使用した動きでサクッと作り直しました。
プレビューの動作も安定しているので、割と楽に使えます。
レコードボタンを押している時に対象を動かせば、対象を移動させることができます。もし動かす対象が既にアニメーション再生しているようならば、OverrideTrackを使うことになるかもしれません。

アニメーションカーブをAnimationClipに変換のススメ
作ったアニメーションは、AnimationClipに変換します。
これには3つの理由があります。
一つはAnimationClipを複数配置する為です。
多くのシューティングゲームでは同じような動きのユニットが存在します。もし複数のユニットの動きに一々キーフレームを打っていたら非常に面倒です。
なのでアニメーションクリップに変換し、ソレをコピーする事で複数のオブジェクトに同じ動きをさせます。
二つ目は位置調整がやりやすくなる為です。
今回は敵の出現パターンや位置は完全にTImeline側で制御しています。
その上で、敵の出現タイミングを数ミリ秒ズラしたい場合、キーフレームで制御していると非常に面倒です。特にTimelineのアニメーションエディタはDopesheetモードが使えないので非常に面倒です。
Clipに変換する事で、クリップの位置を調整するだけで出現タイミングがずらせます。
最後の一つはAnimationCurveを編集するのにAnimationWindowが使えるようになる点です。位置調整が超ラクになります。

Convert to clip trackで変換

動きを量産

カーブエディタが使える&プレビューにも反映される
キャラクターの出現位置を変更する
アニメーションで移動を制御した訳ですが、このままだと同じ位置から同じようにユニットが移動します。できれば出現位置を変更したい所です。

これにはClip Root Motion Offsetを使用します。
ここでアニメーションの開始位置や向きを調整してやると、同じアニメーションでも異なる位置から開始する事ができます。


補足
AnimationClipの変換ですが、いくつか把握しておくべきことがあります。
- AnimationClipが保存されるタイミングは、File > Save Project (シーンの保存等も含むっぽい)のタイミングなので、それまでにクラッシュして落ちるとデータがパー。
- クリップを複製(Ctrl+D)した場合、元のアニメーションとは異なるアニメーション扱い。元アニメーションをD&Dで登録した場合は同一アニメーション扱い。
- 複製で作成したアニメーション・通常の手順で作成したアニメーションはOverideの座標系、元アニメーションをD&Dで登録したものはAdditiveの座標。バグっぽい
弾の発射タイミングを制御する。できればスクリプトで。
次に弾の発射の制御系です。弾をバンバラ出していきます。
流石に弾の発射にAnimationEventを使う訳には行かないので、PlayableAPIで制御しましょう!
と思いましたが、、今回はITimeControlを使用します。
ITimeControlでコンポーネントをTImelineから動かす
ItimeControlを使用すると、TImelineの時間を利用してコンポーネントを操作できます。
もし時間を渡したら弾の位置が(消えてなければ)わかる・・・みたいな実装になっている場合は、Timelineから弾のプレビューも可能になります。
今回は面倒だったので弾を発射するコンポーネントのON/OFFだけを行いました。
撃ってるのはパーティクルです。本当にありがとうございました。

ちなパーティクルは生成破棄を繰り返すのではなくEmitを呼び出してエフェクトを生成したりすると、かなり高速で動作します。というかパラメータが多いせいか生成破棄が
あとColliderも地味に高速です。スプライトアニメーションも(Animationと比べると)高速です。あれ、もうこいつだけでいいんじゃね?
倒さないと進行しない敵
最後に「倒さないと進行しない敵」です。
例えばボスやチュートリアルなど、何らかのアクションを行うまで操作を待つ・・・という事はよくあります。
ディレクターを止めろ!
もし停止中にゲームもしくはTimelineが進行しない・・・といった場合は、Timelineをポーズしてしまうのがいちばん手っ取り早いかもしれません。

ただしPlyableDirectorが停止するので、PlayableDirectorが制御していたアニメーションやカメラワークがニュートラルに戻ります。
もし会話シーンの入力待ち等に使用する場合は、停止の前後でアニメーションのウェイトを減らして通常時のモーションに戻しておくと違和感がなくなります。
タイムリープ系
逆に、条件が達成されるまで同じ動作を繰り返す・・・といったケースもあります。こちらはボス戦とか。
このケースでは、TimelineのTimeを巻き戻してしまうのが楽かもしれません。
クリップの長さを求めて、その時間分巻き戻してやれば、クリップの中でループができます。最早脱出条件を揃えるまでエンドレスエイトです。

ただこちらも一つ注意事項があります。というのも、どうやらTimeを巻き戻して移動した場合はExitやEnter系のコールバックが呼ばれない事があるというもので、もし入退場に何らかの重要な処理がある場合は、注意する必要があります。
まぁPlayableは入退場のコールバックがものすごく使いにくいのである意味問題ないのかもしr
見かけた問題
独自PlayableやControlPlayableで動かした場合、パラメータやActiveがリセットされない事が稀にあります。再現条件は不明ですが、ControlPlayableはActiveをリセット後戻さない事があるみたいです。
Timelineは基本的に「全てのアクセスするオブジェクトがロードし終わった状態」でしか動かせません。多彩なアセットや長いタイムラインを持つ場合、動的にアセットを追加・削除できるように間接的にTimelineで制御するようにした方が良いかもしれません。
負荷は?
エディターは遅い。プレイヤーは思ってたより早い
感想
色々と試しに作ってみました。
まぁ問題は色々とありますが、プレビューできて簡単に微調整できるのは強いですね。
次回は kaiware007氏が何かを書くそうです。
関連
クリップの動作を細かく制御したいなら独自Playable。ただ作るのが超絶スーパーウルトラグレートデリシャスミラクルワンダフル面倒なので、テンプレから作るのおすすめです。