simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:2018.2以降のPrefabとAssetBundleの変化を追う

ココが変わる!Unityの新しいエディターワークフロー
CEDEC2018

いつもお世話になっている「テラシュールブログ」の中の人の発表です。
これだけ Slide Share にアップされていない様子なので、記憶を頼りに
と思ったら Youtube に動画を発見した
www.youtube.com

Prefab と AssetBundle について簡単におさらい

  • Prefab

Prefab はシーン内のゲームオブジェクトをアセット化したものです。
元となるのはゲームオブジェクトなので、Prefab をもとにシーン内でゲームオブジェクトを復元するようにしてインスタンス化します。
このとき Prefab 化(アセット化)したときと同じコンポーネント構成でシーン内に登場します。

  • AssetBundle

巨大な動画、音声、画像ファイルをアセットとして外部サーバーに配置してゲームで必要になったときにダウンロードして使う仕組みで
とても一般的なゲームの実装方法ですが、共通のシステムは存在せず、各社で技術者を雇うか育てるかして独自実装してきました。
このアセットを外部サーバーに置く、必要になったらダウンロードすることを「AssetBundle」と呼びます。

今回の記事で注目するべきこと

■既存の Prefabの問題
Prefab の2 階層目以降の編集ができない
Prefab の下の階層に Prefab を配置できない
とても似ている Prefab 派生オブジェクトを一括編集できない
Prefab の編集の適用は Prefab 単位でしかできない
参照アセットは強制で一括読み込みするので、完了までゲームがフリーズする
AssetBundle に Prefab を置くと動作を確認するのに、長時間かかる(ちょっとした修正にAsset Bundle のビルドが必要)

一言でまとめると、Prefab を扱うために面倒を見るためのエンジニアやコードが必要なことが問題
もっと楽に Prefab を扱いたい

そこで新しい Prefab Workflow が登場しました。←ここが注目するべきこと

■ New Prefab Workflow
1.アイコンからゲームオブジェクトと Prefab を識別できる
2.Prefab の編集した値が太くなるだけではなく、項目の先頭が青く色付く
3.Prefab の何を編集したかを確認する UI が新しく追加される
これには項目を選択すると編集前と編集後のインスペクタを比較する機能がある
Prefab の変更の適用をフィールド単位で行えるようになった
4.Prefab を編集するシーンを開くボタンが追加された(Prefab エディター)
5.Prefab の子階層に Prefab を配置できるようになった(Nested Prefab)
値のオーバーライド(適用)では、どの階層の Prefab がオーバーライド(適用)を行うかを指定できるようになる
発表動画では詳しくデモを通して解説があり、新しく追加された3.Prefab の何を編集したかを確認する UI にて指定できる
6.Prefab の Variant (コンテキストメニューの Create > Prefab Variant を選択で作成)
Prefab なのに個別に値をオーバーライドできるようになり、つまりは、ちょっとだけ内容が違う Prefab を量産できるようになり
さらにオリジナルの変更が Variant にも反映されるので、とても似ている Prefab 派生オブジェクトを一括編集する操作が Variant を使って可能になる
7.モデルの更新でもコンポーネントを保持
今までモデルを差し替えたときにボーン構造が異なると、モデルの Prefab は初期化されてしまっていたが
Nested Prefab の仕組みが入ったので、見た目だけの更新だけで、モデルに与えていた様々なコンポーネントなどが保持されるようになった

■Addressable Asset System
これまでのアセット参照はドラッグ&ドロップだった
ただし直接参照は強制で一括読み込みするので、完了までゲームがフリーズする

既存のロード手段は三つ
・直接参照
・Resources (非推奨)
・AssetBundle (難易度高い)

新しい Unity は以下の2つに変更された
・直接参照
・Addressable Asset System

導入は Package Manager を通して Addressable System を選択して導入できる
コードは以下のように修正する

[SerializeField] GameObject player;
↓
[AssetReferanceTypeRestriction(typeof(GameObject))]
public AssetReference player;

アセットの登録は今まで通りドラッグ&ドロップ

インスタンス生成のコードが変わる

GameObject playerInstance;
playerInstance = Object.Instantiate(player);
↓
GameObject playerInstance;
player.Instantiate<GameObject>().Completed += (op) => { playerInstance = op.Result; }

インスタンス生成が非同期で行えるようになるコード例が登場
(読み込み完了後のインスタンスへの操作も、ラムダ式の中で書ける)

オブジェクトの破棄も変わったので注意

Destroy(playerInstance);
Resources.UnloadAsset(asset);
↓
Addressables.ReleaseInstance(playerInstance);
Addressables.ReleaseAsset(asset);

■Addressables ウィンドウ
AssetBundle に登録したアセットを一覧でアドレスを確認でき、検索用のタグも付けられる

var preloadOp = Addressables.PreloadDependencies("label_name", null);
yield return preloadOp;
public AssetLabelReference hazardsLabel;

var op = Addressables.LoadAssets<IResourceLocation>(hazardsLabel.labelString, null);
yield return op;
hazardLocations = new List<IResourceLocation>(op.Result);

でラベル一致のアセットを事前ロードできる書式が追加されました、便利です。

アドレスから直接オブジェクトを生成する手段もある
Addressables.Instantiate("Address");

さらにこのウィンドウではグループごとに AssetBundle を配置するパスを指定可能
ローカルパスでもサーバーのリモートパスでも可
この設定はプロファイルとして Default, Dev, Release などの名前を付けて、選択一つで切り替え可能になっています。(開発フローを意識したものです)

格納方式も選べる
1アドレスにつき1AssetBundle → pack Separately
グループを1AssetBundle → pack Together

■AssetBundle を用いた高速デバッグ
Play Mode を以下から選択できる
・Fast (AssetBundle 構築しない、AssetDatabase から直接アセット取得 なのでゲーム動作確認時に選ぶとよい)
・Virtual (AssetBundle 構築しない、しかしアセットの依存関係のみを本番と同じにできる、本番想定の関係性構築で利用する)
・Packed (AssetBundle を構築、AssetBundle からアセットを取得 なので本番想定のパフォーマンスをチェックできる)

■PM Profiler
Resource Profiler とも言う
Addressables まわりの操作で生まれたアセットのロード、開放などをプロファイリングすることができる

結論:Addressable Asset System に移行すれば、色々考えなくて良くなる

Unity:パフォーマンス表示uGUIの作り方(2018版)

最近 ECS + Job Systems + Burst Compile と SRP の習熟度を上げましたが、今度は開発中にゲーム内のUIでパフォーマンスをチェックする機構について興味がわいたので
最新の Unity のパフォーマンス計測テクニックを参考に作ってみたいと思います。

ちょっと豆知識ですが、職場の先輩がパフォーマンスを損なうコードを書いた人を犯罪者と呼ぶのですよ。
たとえばプロファイルしたときのCPU処理時間でスパイクが立つとき、その原因となった関数を組み込んだ人を犯罪者と呼びます。

今回手掛けるツールは「あー、なんか犯罪したかも」を、素早く発見することを提供するものです。
他人が書いたコードに手を入れずに
「あなたの書いたこの関数が遅いと思うんです」→「あなたの書いたこの関数が処理時間の8割で30ms要しています、高速化の検討・対処が現在のチームの最優先課題になっています!」
と第一声の内容が変わって、チームメンバーが各々の作業に深く納得しながらお仕事できるようになるわけです、とても素敵ですね!

まずは cedec のこの講演を見てみましょう。

www.slideshare.net

まずはおさらい
Unity でパフォーマンス計りたいと思ったら
Window > Analysis の下にある
Profiler (全体の負荷計測)と Frame Debugger (レンダリングの順番と内容)を表示します。
f:id:simplestar_tech:20180908221157j:plain

Profiler の Overview にて、大雑把に関数ごとの処理時間を知ることができ
Timeline 表示で他のスレッドの処理の様子も見ることができます。(2017.3以降、かつスクリプトに開始と終了のコードを仕込む必要があります)
実装詳細はこちら
Unity - Scripting API: Profiling.Profiler.BeginThreadProfiling

CustomSampler sampler = CustomSampler.Create("Exec");
Profiler.BeginThreadProfiling("My threads", "My thread 1");
sampler.Begint();
// ... スレッドの処理
sampler.End();
Profiler.EndThreadProfiling();

をスレッドの関数内で記述します。

また Editor で開発中は Deep Profile を有効化してさらに細かい関数ごとのパフォーマンスをチェックすることが可能になります。
Android でも adb shell am start -n [パッケージ名]/[アクティビティ名] -e "unity -deepprofiling" コマンドで Deep Profile を有効化できるようになりました。(2017.3より)
Windows マシンに Android を接続した状態で AndroidPlayer を対象に選べば Development Buildを有効にしたアプリであれば Android 実機上でのパフォーマンスも計測できます。
さらに自身で定義した処理区間を Profiler に名前入りで表示することも可能です。
具体的には以下のコードで

    CustomSampler sampler;
    void Start()
    {
        sampler = CustomSampler.Create("MyCustomSampler");
    }

    void Update()
    {
        sampler.Begin();
        // do something that takes a lot of time
        sampler.End();
    }

Profiling.CustomSampler.Begin - Unity スクリプトリファレンス

・Frame Debugger は Unity の描画処理を 1 ステップずつ確認できるレンダリングにフォーカスした確認ツールです。
・Console ウィンドウ
Debug.Log 関数の内容を表示する。(基本ですね)
Console ウィンドウも対象を選択できる機能があり、Player Logging を有効化して Android Player を選べば Development Build を有効にしたアプリのログを表示することができます。

・Script Debugging
こちらも基本ですが、Visual Studio の UI から Unity にアタッチする操作が可能です。
アタッチできれば、Visual Studio の方で配置したブレークポイントでブレークして変数の中身のウォッチなどのデバッグを行うことが可能です。(Visual Studio での開発からUnity開発に流れてきた人にとってはこれが一番うれしいデバッグ機能ですね)
実機の場合でもこのScript Debugging を行うことが可能ですが、ディフォルト設定だとできないという落とし穴を用意していますので、知らない人はずっと苦しむことになります。
具体的にはPlayer Settings にて Mono か IL2CPP を選ぶ項目にて Mono を選んでいる状態かつ、Unity からのビルド時に Development Build かつ、Script Debugging のチェックボックスにチェックを入れている場合のみ、C# Script Debugging を行うことができます。
Visual Studio のUIにて「Unity にアタッチ」の他に「Unity デバッガーのアタッチ」を選べるはずなので、そこで AndroidPlayer(USB)を選択します。
この三つの条件+Visual Studio のUI操作をすべて満たしていないと絶対に Script Debugging できませんので、実機でのパフォーマンスうんたら言う人は必ずエンジニア全員にこのデバッグ方法を周知してから作業に取り掛かってください(この実機でのスクリプトデバッグ方法の設定をチームメンバーに周知しないプロジェクトはこの世に存在しません。実機でデバッグせずに開発を続けることはあってはならない!)
なお Unity 2018.2 からは IL2CPP でも Script Debugging が可能になりました。ただし、こちら .Net 4.x Equvalement にのみ対応しています。(※初回アタッチだけ時間を要する模様)

あとはプラットフォームごとのツールを使って CPU の利用状況などを見ていきます。
PC/Android は RenderDoc
iOS は Isntruments, OpenGLES Frame DEbugger
Android は Systrace, Snapdragon Profiler, GAPID などなど

一瞬ちらつくなど、対処が難しすぎるバグに対応するときはこうした外部ツールを使い始める場面もあるでしょう。

そのほか Unity Prfiler にて示されているものは Recorder API から取得可能です。
具体的には

Recorder recorder = Recorder.Get("Camera.Render");
float ms = recorder.elapsedNanoseconds * 0.00001f ;// で ms オーダーの処理時間を取得

といったコードで取得できます。

メモリの使用状況やヒープサイズなんかも Profiler API から取れるそうです。

// Manager Memory 使用状況
Profiler.GetMenoHeapSizeLong
Profiler.GetMenoUsedSizeLong
// Unity が確保したメモリの使用状況
Profiler.GetRuntimeMemorySizeLong
Profiler.GetTotalReservedMemoryLong
Profiler.GetTotalUnusedReservedMemoryLong

読み込んだアセット一覧なんかも

Resources.FindObjectsOfTypeAll

を利用することでメモリ上にあるアセットを列挙(※Editor 上では余計なものも列挙するので実機上で行う必要あり)
Texture2D, Meth, AudioClip, AnimationClip 一覧を上記関数で調べられるとのこと

なお、こんなことしなくても、リソースごとにどれくらいメモリを使っているかを示す新しい Memory Profiler が 2018.3 から Package Manager 経由で Alpha Preview できるようになるとのこと
これからもっとメモリのプロファイリングは、取得したい情報が取れるようになっていくとのことです。

最後に示されたのは実機で Profiler のログを残すという手段

Profiler.logFile = Path.Compine(Applicaton.persistentDataPath, "profiler.log");
Profiler.enableBinaryLog = true;
Profile.enabled = true;
// ... 計測対象ループ
Profile.enabled = false;
Profiler.logFile = null;

こうして記録したファイルを実機から取得するには
Android だと adb pull filepath で PC 側にファイルを取得可能
iOS だと info.plist でファイルアクセス許可を追加すると、iTunes のアプリ項目からファイルのダウンロードが可能になります。
そうして手に入れたファイルは Unity Profiler の Load ボタンからファイル選択して読み込み可能です。

さて、ここで大問題!
Unity Profiler は最新の 300 frame しか表示することができないので、それより長い Profiler ログを渡しても全部確認することができません!
そうした問題に対応するようにプラグインを発表者の方は作成されています。
github.com
300フレーム問題で苦しみ出したら、このプラグインを使わせてもらいましょう。

本当に最後は 2018.2 より提供された ResourceManager のための Profler
RM Profiler の話で締めくくられました。

ここまでの話を、再度おさらいするのに、テラシュールブログの方の解説も読むと漏れなく学習できると思います。(いつも感謝です)
tsubakit1.hateblo.jp

■ここから本題
スライドのチェックはここまでにして
本題の実機上で一目でわかるプロファイル情報表示UIを作るための書式確認を始めたいと思います。

と思ったら、発表スライドにて、そのUIツールの実装へのリンクが示されていました。
github.com

導入の仕方:
リポジトリをダウンロードしたら MainLoopProfiling.unitypackage を起動して Prefab と .cs ファイル x4 をインポート
Prefab をシーンに配置すると UI が完成

ソース内容はここまで読めばわかると思いま…いや、わからなかったですね。
実装を詳しく見ていくと Unity 2018.1 から使える PlayerLoop を活用したものであることがわかります。
色々なループの型があるのですが、代表的なものとしてこんなものが定義されています。

namespace UnityEngine.Experimental.PlayerLoop
{
    public struct Update
    {
        public struct ScriptRunBehaviourUpdate

        public struct DirectorUpdate

        public struct ScriptRunDelayedDynamicFrameRate

        public struct ScriptRunDelayedTasks
    }
}

こうした型を指定して、例えば ScriptRunBehaviourUpdate の前後に関数を仕込ませ、開始と終了の間の時間を Time.realtimeSinceStartup の値で計測
これを集計して UI のアンカーポイントをずらしながら色付きのバーを動かしていました。
Android の MultuThreadRendering の仕組みまで考慮して Rendering 時間を計測するコードが書かれており、私が手を入れる部分が無いですね
FPS テキストは 1 秒に一回の頻度で更新するように書かれていました。
Unity 2018.1 以降で開発される方はこの計測器を配置してゲームを開発されるとよろしいのではないでしょうか?

こちら Android (Xperia XZs Android 8.0.0) で Basic Rendler Pipeline の描画シーンをキャプチャした画面です。
f:id:simplestar_tech:20180909180934p:plain

Main Thread はレンダリングに比重を置きすぎかもしれませんね。(ビルド設定では MultiThreadRendering 有効化しているけど、実機が対応できていないのかな?)
ただ 60 FPS 出ているのはさすが

もうすぐ AppleiPhone の新しい発表が待っているし、そろそろ iOS で開発してみたいもんです。
新しい iPhone 買っちゃおうかなぁ
Mac Book Air も買っちゃおうかなぁ

Unity:ScriptableRenderPipeline(SRP)の書式確認

Unity の Scriptable Render Pipeline(SRP) とは端的に言うと、Unity がフレームをどのようにレンダーするかをデベロッパーが C# で制御できるようにするものです。(大事なことなので何度でも)

前回の記事でSRPの導入方法はバッチリ
simplestar-tech.hatenablog.com
今回は、前回記事の最後に確認した最も単純な BasicRenderPipeline の実装について確認していきます。

具体的には次の BasicRenderPipeline.cs コードを詳説して終わります。
github.com

定義されているクラスは三つ
1.public class BasicRenderPipeline : RenderPipelineAsset
Unity のプロジェクトから選択できる SRP アセットを定義しています
2.public class BasicRenderPipelineInstance : RenderPipeline
上のアセットに紐づくパイプラインで、実装は次のクラスの Render 関数を呼ぶだけ
3.public static class BasicRendering
ScriptableRenderContext renderContext, Camera cameras を受け取って、実際にパイプラインを実装している静的クラス

終始 public bool UseIntermediateRenderTargetBlit; フラグで分岐するコードがあるが、これはシーンからユーザーがチェックボックスで設定するもので、基本 false の値となっている。
シーンで切り替えられるように配置したフラグをネストが深いところまで連絡しているコードが全体を汚しているだけなので
最初は UseIntermediateRenderTargetBlit の連絡コードと分岐先の実装は無視して読むと、認識負荷は大きく落とせます。

そうした上で BasicRendering クラスの実装の中身は次の通り
1.ScriptableRenderContext renderContext, Camera cameras を受け取る、静的な Render 関数の実装
2.その Render 関数で利用する ConfigureAndBindIntermediateRenderTarget 関数(UseIntermediateRenderTargetBlit == true の時のみ実行するので無視)
3.その Render 関数で利用する BlitFromIntermediateToCameraTarget 関数(UseIntermediateRenderTargetBlit == true の時のみ実行するので無視)
4.その Render 関数で利用する SetupLightShaderVariables 関数
5.その Render 関数で利用する GetShaderConstantsFromNormalizedSH 関数

最初に通しで読む関数は 1.4.5.だけです。
1.では camera のループを回し、camera から見えないオブジェクトを削ぐようにカリングする実装から始まり
コンテキストに camera の設定を入力して、CommandBufferPool からコマンドを取得し depth のみクリアしています。
続いて global lighting shader variables を最大 8 個まで更新して RenderQueueRange.opaque と不透明オブジェクトのみを扱うようにフィルタリングして、近い順に描画しています。
そのあとに余ったピクセルに Skybox を描き、最後に RenderQueueRange.transparent と半透明オブジェクトのみを扱うようにフィルタリングして、遠い順に描画しています。
camera 単位の描画処理を記述したら context.Submit(); して次の深度のカメラについて処理していきます。
stereoEnabled フラグは XR でデバイスを検知しない限り false なので、ほとんどのコードを無視してパイプラインの実装を読むことが出来ました。
以上です。

これはわかりやすい。
では少し弄ってみましょう。

不透明オブジェクトとSkyboxを描画してから半透明オブジェクトを描画していますが
半透明オブジェクトの描画を先頭に持ってくるとどうなるでしょうか?

f:id:simplestar_tech:20180909125955j:plain

半透明オブジェクトが…

f:id:simplestar_tech:20180909130230j:plain

不透明オブジェクトで上書きされてしまいました。
Unity にはオブジェクトごとのレンダリング結果を見る Frame Debugger がありますので、そちらを使って描画順序を確認してみましょう。
するとこんな感じ

f:id:simplestar_tech:20180909131718g:plain

なるほど、半透明を描画してから不透明で上書きしていることがはっきりわかりますね。
Sort フラグもちょっといじりますか
None を設定したところ、Plane の描画順番が早い段階に移動してしまいました。
これでは、後で不透明オブジェクトで上書きされてしまう無駄なピクセルの計算が走ってしまいます。

f:id:simplestar_tech:20180909132447j:plain

なるほど、こんな感じで FrameDebugger と連携しながら、シーン描画を最適化するようにパイプラインを弄っていけば、かなり描画パフォーマンスを意識したパイプラインを構築できるようになっていくのではないでしょうか?
また少しレベルアップ出来た気がします。

Unity:2018のScriptable Render Pipelineに慣れる

Scriptable Render Pipeline(SRP) とは端的に言うと、Unity がフレームをどのようにレンダーするかをデベロッパーが C# で制御できるようにするものです。

ついに Unity のブラックボックスに手を入れられる時代が始まろうとしています。
これを活用できるかどうかがゲーム会社として生きるか死ぬかの分かれ目になりそう。
たくさんの同一オブジェクトをシーンに配置するようなゲームでは特に、パフォーマンスを上げる手段として使えるため
SRPを使えるようになっておかないといけないでしょう。

参考資料は先週開催された cedec 資料のこちらです「Scriptable Render Pipeline を使ってみよう」

www.slideshare.net

まずは今までのブラックボックスレンダリングをホワイトボックスに切り替える具体的な操作から確認します。

参考資料のさらに参考資料として、凹みさんの記事を読みました。
tips.hecomi.com

今ある Forward, Deferred レンダリングでうまくない点
既存の問題点を洗い出して、解決策としてのレンダリングを制御できる API の用意は自然な流れ
それが Scriptable Render Pipeline (SRP) という理解が得られました。

今回試した環境
Unity 2018.2.6f1

では SRP に切り替える方法ですが
Package Manager を起動して SRP 関連をインストールします
Create > Rendering > Lightweight Pipeline Asset
を選択してプロジェクトに SRP オブジェクトを作成

メニューの Edit > Project Settings > Graphics を選択してインスペクタを表示し
f:id:simplestar_tech:20180904215541j:plain
この Scriptable Render Pipeline の項目に、作成した SRP を指定します。

以上

確かに描画が切り替わったようです。
マテリアルが不正な状態となって、Unity特有のどぎついピンク色でべた塗りされてしまいました。
Lightweight Pipeline Asset オブジェクトを選択してインスペクタを確認すると次の通り
うん、解説がないとわからないですね。

f:id:simplestar_tech:20180905065748j:plain

ここでスライド資料にて紹介されていた次の ReadMe をすべて読みました。(SRPのソースコードもここから確認することになります)
github.com

読んでいて、結局こちらも必見でした。(読みました)
blogs.unity3d.com

これらを読んで新しく手に入った知見は
1.シーン内のマテリアルは Lightweight Pipeline 用に作り直す必要があること
2.その新しいマテリアルに作り直す具体的な方法
3.プロジェクト設定はスクリプトから GraphicsSettings.renderPipelineAsset プロパティでアサインできること
でした。(それだけ)

具体的な Lightweight Pipeline の導入手順についてはこちらの方が分かりやすかったです。
tsubakit1.hateblo.jp

では最後に Sample Scenes のリポジトリとして紹介されている次のリンクを確かめます。
github.com
HDRP, LWRP 両方が必要なので、PackageManager から設定しました。
TestbedPipelines フォルダの BasicRenderPipeline シーンから確認してみましょう。

実行するとこんな絵になります。
f:id:simplestar_tech:20180908120059j:plain

シーンのヒエラルキー内のスクリプトコンポーネントはたった一つで以下のコードでした。

using System;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;

[ExecuteInEditMode]
public class SceneRenderPipeline : MonoBehaviour
{
    public RenderPipelineAsset renderPipelineAsset;

    void OnEnable()
    {
        GraphicsSettings.renderPipelineAsset = renderPipelineAsset;
    }

    void OnValidate()
    {
        GraphicsSettings.renderPipelineAsset = renderPipelineAsset;
    }
}

シーンで設定した RenderPipelineAsset をエディタ変更時またはシーン開始時に GraphicsSettings に設定するだけ
実際に設定したのは BasicRenderPipeline です。
こちらはまさに自作の RenderPipeline で次の実装から作られていました。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;
using UnityEngine.XR;

// Very basic scriptable rendering loop example:
// - Use with BasicRenderPipelineShader.shader (the loop expects "BasicPass" pass type to exist)
// - Supports up to 8 enabled lights in the scene (directional, point or spot)
// - Does the same physically based BRDF as the Standard shader
// - No shadows
// - This loop also does not setup lightmaps, light probes, reflection probes or light cookies

[ExecuteInEditMode]
public class BasicRenderPipeline : RenderPipelineAsset
{
    public bool UseIntermediateRenderTargetBlit;

#if UNITY_EDITOR
    [UnityEditor.MenuItem("Assets/Create/Rendering/Basic Render Pipeline", priority = CoreUtils.assetCreateMenuPriority1)]
    static void CreateBasicRenderPipeline()
    {
        var instance = ScriptableObject.CreateInstance<BasicRenderPipeline>();
        UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/BasicRenderPipelineTutorial/BasicRenderPipeline.asset");
    }

#endif

    protected override IRenderPipeline InternalCreatePipeline()
    {
        return new BasicRenderPipelineInstance(UseIntermediateRenderTargetBlit);
    }
}

public class BasicRenderPipelineInstance : RenderPipeline
{
    bool useIntermediateBlit;

    public BasicRenderPipelineInstance()
    {
        useIntermediateBlit = false;
    }

    public BasicRenderPipelineInstance(bool useIntermediate)
    {
        useIntermediateBlit = useIntermediate;
    }

    public override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
    {
        base.Render(renderContext, cameras);
        BasicRendering.Render(renderContext, cameras, useIntermediateBlit);
    }
}

public static class BasicRendering
{
 ...
}

なるほど、エディターで扱える Asset と実行時に利用する Pipline を合わせて実装する必要があるのですね。
気になる BasicRendering の実装はぜひ最新の次のページをご覧になってください。
github.com

カメラを指定して SkyBox を描くコードを見つけられると思います

// Draw skybox
context.DrawSkybox(camera);

このコードをコメントアウトしてみたところ、すぐに反映されて背景の Skybox の描画が行われなくなりました。

f:id:simplestar_tech:20180908125329j:plain

おおー、今私は Unity の Rendering Pipeline を直接いじれていますよ!
ここを足掛かりに、すこしずつ SRP に慣れていこうと思います。

Unity 最高!

Unity:job-system-cookbookからC#JobSystemの実例を確認

ちょっと前の私の記事
simplestar-tech.hatenablog.com
こちらでメッシュの変形やCubeの移動、画像処理などのサンプルをみつけたので、今回はそのサンプルの実装を確かめていきます。

Unity 2108.2.5f1
PackageManager にて
Burst 0.2.4-preview.26
Entities 0.0.12-preview.11
を有効化して動作確認しました。

ExampleScenes は 8つありますが、その中でも画像処理を行う WebcamProcessing のコードを追ってみます。
注意点として Webカメラから画像情報を引っ張ってくる処理と、その画像情報をテクスチャに適用する処理がメインスレッドの処理の99%を占めているので
その辺のコードを外してみるとパフォーマンスのすごさが体感できると思います。(いつか、カメラ画像情報の取得のオーバーヘッド減るといいね、画像処理研究してる人は50年ほど悲しんでいる)

コードを見ていて最初に衝撃を受けたのが次の SliceWithStride
Unity.Collections.NativeSlice_1.SliceWithStride - Unity スクリプトリファレンス

        m_Data = new Color32[m_WebcamTextureSize.x * m_WebcamTextureSize.y];
        m_NativeColors = new NativeArray<Color32>(m_Data, Allocator.Persistent);

        var slice = new NativeSlice<Color32>(m_NativeColors);
        m_NativeRed = slice.SliceWithStride<byte>(0);
        m_NativeGreen = slice.SliceWithStride<byte>(1);
        m_NativeBlue = slice.SliceWithStride<byte>(2);

NativeSlice は native 故に便利関数が用意できるんですね。
要素単位のオフセット指定で byte を取り出せる配列になるとか、C++でできた高速アクセステクニックが復活してきた

毎フレーム呼ばれるのが以下の関数

    void BurstExclusiveOrProcessing(NativeSlice<byte> r, NativeSlice<byte> g, NativeSlice<byte> b, ref JobHandle handle)
    {
        var redJob = new ThresholdExclusiveOrBurstJob()
        {
            data = r,
            threshold = m_ColorThreshold.r,
            widthOverLineSkip = m_WebcamTextureSize.x / lineSkip,
            height = m_WebcamTextureSize.y,
        };

        var greenJob = new ThresholdExclusiveOrBurstJob()
        {
            data = g,
            threshold = m_ColorThreshold.g,
            widthOverLineSkip = m_WebcamTextureSize.x / lineSkip,
            height = m_WebcamTextureSize.y,
        };

        var blueJob = new ThresholdExclusiveOrBurstJob()
        {
            data = b,
            threshold = m_ColorThreshold.b,
            widthOverLineSkip = m_WebcamTextureSize.x / lineSkip,
            height = m_WebcamTextureSize.y,
        };

        var length = m_NativeRed.Length;
        var rHandle = redJob.Schedule(length, 1024);
        var gHandle = greenJob.Schedule(length, 1024, rHandle);
        handle = blueJob.Schedule(length, 1024, gHandle);
    }

ThresholdExclusiveOrBurstJob を引数を r,g, b と変えながら3つ定義し、r, g, b の順でスケジュールしています。
Job 処理は 1024 画素ごとに行うように指定している

処理を理解するうえで重要なのは次のジョブの実装

[BurstCompile]
public struct ThresholdExclusiveOrBurstJob : IJobParallelFor
{
    public NativeSlice<byte> data;
    public byte threshold;
    public int height;
    public int widthOverLineSkip;

    public void Execute(int i)
    {
        bool operateOnThisPixel = (i % height) < widthOverLineSkip;
        bool overThreshold = data[i] > threshold;
        data[i] = (byte)math.select(data[i], data[i] ^ threshold, overThreshold && operateOnThisPixel);
    }
}

math.select は条件分岐を提供する Unity の新 math ライブラリの関数
どんなものがこれから登場するかは次のスライドの 33 p で予告されていた

www.slideshare.net

math ライブラリも NativeSlice もドキュメント整備はこれからって感じで、しっかりした説明にはまだたどり着けない

論理 XOR 演算子も復習しておかないと、すぐにはコードを読んでイメージできない
^ 演算子 (C# リファレンス) | Microsoft Docs

とりあえず ECS を抜いた C# Job System + Burst の書式で書かれた非常に短いサンプルですね。
理解できました。

メッシュ操作の方が気になるので、もう少しサンプルを見ていきましょうか

MeshComplexParallel というクラスの実装をみてます。

    NativeArray<Vector3> m_Vertices;
    NativeArray<Vector3> m_Normals;

    Mesh m_Mesh;

主な登場人物はこんな感じ(人ではないけど)

頻繁な更新を行うためにメッシュを最適化するのに

        m_Mesh = gameObject.GetComponent<MeshFilter>().mesh;
        m_Mesh.MarkDynamic();

        // this persistent memory setup assumes our vertex count will not expand
        m_Vertices = new NativeArray<Vector3>(m_Mesh.vertices, Allocator.Persistent);
        m_Normals = new NativeArray<Vector3>(m_Mesh.normals, Allocator.Persistent);

こんなことしてます。
マジかよ!!超いいこと知ってしまったぜ。(ていうか、最初から知っておくべき

頂点を更新するのにパーリンノイズを利用していたり、頂点自体が Vector3 と blitable ではないので Birst Compile は諦めている様子

    struct MeshModJob : IJobParallelFor
    {
        public NativeArray<Vector3> vertices;
        public NativeArray<Vector3> normals;

        public float sinTime;
        public float cosTime;

        public float strength;

        public void Execute(int i)
        {
            var vertex = vertices[i];

            var perlin = Mathf.PerlinNoise(vertex.z, vertex.y * vertex.x);
            perlin *= strength * 2;
            var noise = normals[i] * perlin;
            var sine = normals[i] * sinTime * strength;

            vertex = vertex - sine + noise;

            vertices[i] = vertex;

            normals[i] += Vector3.one * cosTime * perlin;
        }
    }

とても参考になります。
マインクラフト風の地形生成をこんな感じで Job を使って高速化してみたいですね

Unity:CEDEC2018のECSの発表から学ぶ

Unity にはコンポーネント指向という作法がここ10年間築かれてきましたが、他のゲームエンジンに並ぶような速度で計算するためには、そのコンポーネント指向をやめる必要があります。
ECSと呼ばれる新しいシステムを導入して、パフォーマンスを上げていくことになります。

ここまでECSでパフォーマンスが上がる仕組みをなんとなく理解してきたけど
ここで、もう一度 ユニティ・テクノロジーズ・ジャパンの発表資料を使って勉強していくことにします。

今回参考にする発表資料はこちら
「CPUを使い切れ!Entity Component System(通称ECS)が切り開く新しいプログラミング」
CEDEC2018

発表動画はそのうちこちらに追加されるのかな?
CEDECチャンネルYouTube版 - YouTube

これからは Unity を使う = ECS + Job Systems の書式でゲーム作る
という式が成り立つので、早いところ慣れておかないとUnityの現役エンジニアでも、一年後くらいに仕事無くなっちゃうと思います
人は誰しも常に勉強し続けて、最新技術に追従していかないと生きていけないんですよね

さて今回のECSへの移行ですが C++ で仕事してきた人たちがまた脚光を浴び、C# であまり見かけないポインタって何?、CPUにやさしいメモリ配置って何?って言ってた人が立場を失うドラマが各所で始まるわけです(ウソ)

発表スライド資料はこちら、簡単に読んで内容をまとめてみます。

www.slideshare.net

コンピュータが作られたのは第二次世界大戦中、ちょうど祖母が成人したころです。(祖母は今も元気に生きています。歴史浅い)
仕組みはずっと変わっておらず、集積率だけが劇的に向上して今のスマホがあります。(大枠でとらえると)
大枠でとらえたコンピュータの仕事は、CPU がメモリから情報を引き出して、足し算した結果をメモリに戻す作業なのですが
単純な処理を大量にこなす処理の中、まったく同じ計算式を実行するときに、一気に複数の式を実行する機能があります。
これには特定のメモリ領域に、決まったルールで情報を配置する必要があるのですが、要するにメモリに効率的にデータを詰めておけば、コンピュータの仕事が早く終わるというアイディアが存在します。

ゲームオブジェクトに共通の要素、例えば Transform Matrix や Position, Rotation 情報など、これらを更新する処理を対象に、
登場するデータをメモリに効率的に配置したいので、その配置処理に必要な情報を拾えるように ECS へ必要最低限の情報提供を行う必要があります。

ということで、その ECS が求める情報と、渡し方のコード書式を具体的に覚えることが、これからの Unity 開発時代を生き抜くことにつながります。

そこで、具体的には発表資料の 33p ~ 35p の部分の絵を覚えるのが良さそうです。
情報を行列と見立てた場合
Entity が列ベクトル(column)
Component が行列の要素(element)
System が行ベクトル(row)
CPUがアクセスするメモリの構造をイメージしながらコードを書く感じですね。

この行列の図から、Entity は Component を要素とする列ベクトルなので、プログラムでは Entity に Component を登録する記述が必要になります。
そこで Entity へ渡すComponet を列挙するコードが ArcheType 作成コードです。

ECS は全ての Entity がどのような Component を持つのかを把握してメモリ配置の効率化を行いたいので、すべての ArcheType の登録は EntityManager を介して行うことになります。
登録した ArcheType を指定して、初めて Entity を作成するコードが書けるようになります。

資料の 39p から 44p までの書式は
行列要素の Component 定義
行列の列ベクトルの Entity の定義
その Entity の作成
作成した Entity の Component へ値の代入 (SetComponentData)
となっています。

EntityComponentSystem では動的に Entity を定義して利用することになっていきます。
プログラミング時のメンタルモデルを変えないといけないですね。(型は基本コンパイル時(静的)に決めていたと思いますので)

BurstCompiler を有効化する時の属性が
[BurstCompile]
とわかりやすくなっている。(Jobの属性に追加するのは変わらず、昨日見た Unite Europe 2017 動画だと、もっと長いやつだった)

Job 構造体の Execute 関数内部のみが高速化用のコードに変換されるが、参照型にアクセスできない、static 変数にアクセスできないという制約あり(逆に副作用とされる競合状態を事前に避けられるのでうれしいかも)
ところで、デバッグ時の Debug.Log を Execute 関数に仕込みたいですよね、その気持ちわかる!ということで関数の属性に [BurstDiscard] を与えると、Burst するときに除外されるので、非 Burst のデバッグ時に利用できることが紹介されています。

マルチスレッド処理のおさらい
コンテキストスイッチ
いつも仕事の効率化について使う単語だけど、CPUに割り当てるスレッドの切り替えも同じ単語使う

そして最重要なのが ComponentDataArray

残念ながら、このパワポ資料単体では ComponentSystem の動きをイメージしきれません。
いったんイメージ作成のための要素知識を作成する作業として次の記事を読んでおきます。
tsubakit1.hateblo.jp

さらに残念なことに、[Inject] などの属性のイメージがまだつかめないことがわかりました。

パワポ資料に戻って、コンポーネント指向のこれまでの実装と、ECS によるこれからの実装の変化についてみていきます。
MonoBehaviour → JobComponentSystem
MonoBehaviour.Update 関数 → JobComponentSystem.OnUpdate 関数

行列で ECS を表した時、行ベクトルが System であることを示しました。
System は複数の Entity から抽出した同一の ComponentData 配列であることをイメージできます。

その ComponentData 配列こそ ComponentDataArray であり、具体的に利用するために ComponentSystem クラス内に注入(Injection)します。(なんか表現エロいですね)
正確には複数の ComponentDataArray を Group として定義して、その Group に [Inject] 属性を与えて注入することを宣言します。
この ComponentDataArray の Group の Injection が只者じゃないってことで、発表資料では面白いと紹介されています。

実装例では ComponentDataArray と IJobParallelFor の組み合わせで、Group の Injection の代わりに Job の依存関係を記述することで示されています。
ComponentDataArray からの読み出しと書き込みの例が示されていますが

ECS 学習中に、Job System との連携コードが示されて、急激に認識負荷が上昇していますね。
ここもっと優しい解説が必要なんじゃないかな?

そこで、私の解説を追加します。
Job の依存関係は前回の記事にて
[UpdateAfter(typeof(DamageSystem))]
といった属性を ComponentSystem クラスに設定することで解決できると示しましたので、これで行列の行ベクトルの処理の実行順を ECS に教えることができます。
この記事を書いた後に、とても理解の助けとなる解説記事が作られましたので紹介します。
tsubakit1.hateblo.jp
プロファイラからもEntitySystemの実行順が確認できるのは助かりますね。(精神が)
さらに OnUpdate 関数内の Job の Schedule にて、このクラス属性の依存関係を参照できるように
AddDependency(job.Schedule(m_transforms, GetDependency()));
と ComponentSystem 側から Job Handle を取得して、ECS と Job System との融合を果たします。

やっと書式に納得いく理解できた。

あとは行列の要素である Component にどんな型が指定できるかの解説が続きます。
IComponentData で書ける型は blitable なもののみ
blitable ってのは、よく DllImport などで渡しできる型が限定される例と同じで、メモリサイズが確定する型のみ指定可能
ゆえに NativeArray もだめです。

高度な解説ですが、Job 内の Execute 処理中に Entity を生成したいという要望にも応えるギミックがあることが示されています。
具体的には EntityCommandBuffer と BarrierSystem の存在です。
Job の中での Entity 生成は、いったんその Job がすべてのスレッドで完了し終えた時に、Entity の生成を行うということで
コードを書いているときに、やっぱりスレッドの動きを想像しながら書かなければいけません。(ここはこれまでのマルチスレッドの記述で気を付けていたことと同じですね)

この辺の書式は使っていかないと、感覚をつかみづらそうですね
すべての Job を停止してから、キューに積まれたコマンドを実行する遅延実行の機構が BarrierSystem で
そのコマンドのキューが EntityCommandBuffer と覚えておきます

あとはゲームロジックから見た ECS ですね。
OnUpdate で一気に複数の Entity の ComponentData を処理することになりますが、やっぱり特定の Entity について特別に処理を分岐したいじゃないですか。
例えば大量のミサイルの追尾となったとき、ミサイルが追尾する対象は Entity なので、その Entity の ComponentData を取得したいケース
エンティティから Component Data を取得可能とはそういうことで
ComponentDataFromEntity が用意されています。
ほかにも、Entity が存在しているかどうかをExists 関数を用いて Job 内で見ることができるので、なんだかんだゲームオブジェクトへの参照が Job 内で使えなくても、ゲーム特有のオブジェクト依存のロジックが組めそうです。

ゲームロジックをどうしても Job で書きたい、ゲームオブジェクトへの参照を Execute 関数内で実現したい、そんなときの書式紹介もありました。
static 変数へのアクセス制限があるので BurstCompile をあきらめることになりますが、Static 変数として定義したクラスのインタフェースを介してゲームオブジェクトの参照を利用したコードを Job 内で実行することができます。

後半は汎用性ではなく、ゲーム特化のテクニックの紹介ですね、実装時に困ったら参考にしてみようと思います。
こんなところでしょうか?

結局作りやすさは従来のコンポーネント指向で、これは無くならずに
今後パフォーマンスが求められる場面で ECS が導入されていくことになる未来が見えてきました。

一度コンポーネント指向で作り、パフォーマンスの負荷が高い場所について、 ECS への移行ができないかを見極める眼力が今回の勉強で備わった気がします。

Unity:C# JobSystemとBurstCompilerそしてECSこと(EntityComponentSystem)の概要

この三つがそろった時、Unity は完全体となって、Unreal は死ぬという噂が流れるほど
Unity のパフォーマンスを向上しつつ、競合状態などを起こさないように安全にコーディングできるようになるとのこと

さて、前回に続いて C# Job System の書式を学んでいます。以下のドキュメント読んでますが
github.com

そのドキュメントにまずは次の動画を事前に見ろ!(意訳)ってありましたので、見ました。
www.youtube.com

内容をざっくりまとめると

1.C# Job System の書式解説
具体的には、NativeArray をはじめ、NativeList, NativeSlice, NativeHashmap, NativeMultiHashmap, NativeMultiHashmap.ConcurentWriter など
みんなが C# でよく使ってきた List, Dictionary に相当するものを unsafe に扱えるものを C# 用に用意したよというもの(すごい、Unity 以外でも欲しい)
そのほか Job として何らかの C# コードを実行するには IJob を継承する struct を定義して Execute に処理書けば動くよ
普通はマルチスレッドに分散するよね、そんなときは IJob の代わりに IJobParallelFor を使うよ
超重要なことだけど、依存関係は Job の Schedule を切ったときに取得できる JobHandle を利用することで、安全に順番に Job を実行できるよ
お魚の群れのシミュレーションコードも、ここまで解説した基本の通りになっているよ

2.BurstComplier (C# Job Compiler)の使い方
まず速くなるのは

No virtual functions
No reference types
No GC
Native Containers
SIMD auto -vectorization
Preciton control (Low, Med, High)

の強い制約をもって最適化するためです
Compiler の恩恵を受けたければ、先ほど紹介した Job 構造体の attribute 宣言に
[ComputeJobOptimizationAttribute(Accuracy.Med, Support.Relaxed)]
を書くだけ(会場から笑い)

3.これまでの書式からの書き換えの簡単さの紹介
パフォーマンス気にしているなら Update を MonoBehavior ごとに書かずに、どっかにまとめて for 文を回すようにしてきたと思います。
みなさんそうしてます?(会場:はーい)クールだね
そんなギミックコードの Update 内の処理を Job の Execute に置き換えるだけで、ほら、既存のコードの高速化も超導入しやすいでしょう?(拍手)

4.ECS(EntityComponentSystem)
みんなが今まで書いてきたコンポーネント指向のコードは

GetComponent<Transform>()

といった形でメモリのあちこちから参照を引っ張ってきて、コンピュータは最適化に困っていたわけです。
解決するために ECS の書式に書き換える必要があるわけですが
具体的にはデータだけが定義されているような MonoBehavior は IComponentData を継承する struct に差し替えましょう
出現しているゲームオブジェクトをまとめて処理していたようなクラスは ComponentSystem を継承するクラスに差し替えて

List<Transform>

フィールドは

[InjectTuples]
public ComponentArray<Transform>

に書き換えて、C++でいうところのポインタ配列にして、連続でメモリアクセスできる状態にするのです。
ComponentSystemクラスの属性に
[UpdateAfter(typeof(DamageSystem))]
といった形で、さらに別の ComponentSystem との依存関係を定義できます。
驚いてほしいのはここから、ComponentSystem を JobComponentSystem に書き換えておけば OnUpdate 処理にて Job を作成し
AddDependency(job.Schedule(m_transforms, GetDependency()));
と書けば、C# Job System の書式説明で書いた Complete を呼ばなきゃ、みたなこともなく競合が起きなくなるし、これが最適化処理に響いて極めて高速化するわけ。
イメージとしては低レベルレイヤーですべての会話が成り立つという感じで、コードは競合状態を気にせず値変更とか書けるようになる!(会場:すごーい拍手)ハッピー

5.質疑応答
ネイティブ同士の会話は聞き取れないー

1時間の動画だけど、C# Job System, Burst Compiler, ECS などのキーワードと実装イメージの連絡を行いたいときは、こちらの動画をじっくり視聴することをオススメされるのもわかる気がした

さーて、サンプルコード読んでいくぞー
今なら読める気がする