Canvasの処理負荷を減らす方法.

# はじめに
Canvas内に動いているものがある場合は、処理負荷を軽減するためにCanvasを分けるという方法があります。
動いているものを一つずつCanvasにしていくのか、ある程度まとめた単位でCanvasにしていくのか、勘所がつかめなかったので試してみたいと思います。
`PostLateUpdate.PlayerUpdateCanvases`にかかっている時間の調査になります。

注意：
今回の計測はUnityエディタで行っているため、AndroidやiOSなどの端末での効果を保証できません。
それぞれでの処理負荷は、それぞれの端末で計測を行うようお願いします。

# 使用したバージョン
Unity 2018.2.8f1

# 調査環境
調査環境として動かない白いImage5000個と動く赤いImageを1個を対象とすることにしました。

![hierarchy-default.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/8dd0b3a4-cf1a-4ae8-e07d-14260b7e4481.jpeg)

Hierarchyは上記の状態にし、スクリプトでWhites以下にImage（White）を増やすようにしています。

```cs:Generator.cs
using UnityEngine;

public class Generator : MonoBehaviour
{
    [SerializeField]
    private int _instanceNum = 5000;

    [SerializeField]
    private GameObject _image = null;

    private void Awake()
    {
        for (var i = 0; i < _instanceNum; i++)
        {
            var instance = Instantiate(_white, transform);
        }
    }

}
```

実行中は以下のような状態になります。

![hierarchy.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/79538c42-fa60-64e2-be38-6dd9f007c0c7.jpeg)

Whites以下に5000個の`White(Clone)`が並んでいます。
全てのオブジェクトで位置などが同じになっていますが、一旦問題なしとして進めていきます。

動く赤いImageはアニメーションのループで動かすようにしています。

![animation-red.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/57c57fb0-ea72-ba13-e365-a0f8b36e0fd9.jpeg)

処理負荷の比較はProfilerを利用して、処理にかかる時間`Time ms`を比較しています。

# 動いているものがあると処理負荷がかかる
赤い四角形が動いていない場合と動いている場合の処理にかかる時間を比較してみました。

![not-move-red.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/fdda43df-f8e7-b8ac-31ea-a57cf152c1f8.jpeg)
![move-red.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/74c8e5d5-b439-f5b7-9f3b-8e9d375bec75.jpeg)

Unityエディタ上ではありますが、赤い四角形が動いていない方が16ms以内に収まっているのに対して、赤い四角形が動いている方は激しい処理落ちが発生しています。

# 何をすると軽くなるか
上記の状態から処理を軽くするために以下の2点を試してみます。

## 動いているものにCanvasを付けてみる

![add-canvas.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/353223d3-47c2-22b8-6309-bcb2b8417622.jpeg)
![parent-canvas.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/c7a62f35-ab4b-568e-d9ca-c2dc2ace49e0.jpeg)

それぞれ動いている赤い四角形にCanvasを付けたものと動いている赤い四角形の親オブジェクトにCanvasを付けたものになっています。
とてもわずかですが、親オブジェクトにCanvasを付けた方が処理が軽くなったという結果になっています。
（端末でも処理が軽くなるかは、計測していません。）

## 白いオブジェクトの半分を非表示にする

![hide-half.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/b312d1a6-ea7e-cba6-8a24-74ba48270ad6.jpeg)

表示している白い四角形を半分だけ非表示にしてみました。
まだまだ処理は重いものとなっていますが、最初の1/4くらいの処理時間となっています。

# たくさんのオブジェクトが動いていた場合はどうするか
今度は白い四角形5000個と赤い四角形100個に変更し、たくさんの赤い四角形が動いている状態にします。
(赤い四角形は同じ動きをしている状態です。)

![hierarchy-many-red.PNG](https://qiita-image-store.s3.amazonaws.com/0/111339/76725852-0e9f-656b-4fa3-408923020819.png)

シーンのHierarchyはこんな状態になっています。
Redはスクリプトでインスタンスを増やしています。

![move-many-red.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/51479a16-ce02-fb12-09e7-67241efe298e.jpeg)

Canvasを分けていない状況では、元々処理負荷が高かったこともあり、動いている赤い四角形が増えても処理負荷が大きく増えることはありませんでした。

![add-canvas-many-red.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/2f737356-0ff5-e1d1-e760-19cbd3120d7c.jpeg)

動いているオブジェクトにCanvasを付けてみました。
大幅な処理軽減ができており、おおよそ5msくらいの処理時間となっています。
動いているオブジェクトが1個だったときと比べると処理負荷が増えている状態です。
（今回の値では約20倍の値となっていますが、端末によって変わるはずです。）

![parent-canvas-many-red.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/2ca9e7d2-4ca3-0652-fdef-23f5cee56308.jpeg)

動いている赤い四角形たちの親オブジェクトにCanvasを付けてみました。
処理にかかった時間が1ms以下になっています。
動いているオブジェクトが1個だったときと比べてもほとんど遜色のない結果となっています。

# どんなことが「動いている」に含まれるのか
あるCanvas内にある大量のオブジェクトのうち一つでも動いているものがあると処理負荷が大きくなることがわかりました。
これまでは、位置を移動させることを「動いている」としてきましたが、他にはどんなことをすると「動いている」と判定されるのか気になったため、特に気になった下記の2点を試してみます。

## 設定されている画像を差し替える
以下のようなスクリプトを用意して毎フレーム`Image.sprite`を変更するようにしてみました。

```cs:ChangeSprite.cs
using UnityEngine;
using UnityEngine.UI;

public class ChangeSprite : MonoBehaviour
{
    [SerializeField]
    private Image _image = null;

    [SerializeField]
    private Sprite[] _sprites = null;

    private int frameCounter;

    private void Start()
    {
        _image.sprite = _sprites[frameCounter];
    }

    private void Update()
    {
        frameCounter = (frameCounter + 1) % _sprites.Length;
        _image.sprite = _sprites[frameCounter];
    }
}
```

![change-sprites.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/8c8db425-145f-b912-bf3a-2e2e108a7ada.jpeg)

結果として位置を移動させていたときと同様に処理負荷が大きくなっていました。
`Image.sprite`を変更することも「動いている」と判定されるようです。

## レンダーテクスチャを表示する
UIを写しているカメラとは別のカメラで動いているCubeを撮影し、それをRednerTextureに描画するようにしました。
そして、UI側でRenderTextureをRawImageで表示するようにしてみました。

![render-texture.jpg](https://qiita-image-store.s3.amazonaws.com/0/111339/a26b2167-552b-1aa0-df2c-2ff9bcd0665d.jpeg)

結果としてほとんど処理負荷が大きくなっていませんでした。
RawImageで表示しているRenderTextureの内容が変化しても「動いている」と判定されないようです。
（別途、カメラの描画負荷などは増えています。）

# まとめ
- Canvas内のオブジェクト数が処理負荷に影響している
- 動いているオブジェクトにCanvasを付けることが簡単にできて効果が高い
- 複数のオブジェクトが動いている場合は、それぞれにCanvasを付けるよりもまとめて付けた方が効果が高い
- Image.spriteの変更も動いていると判定される
- RawImageに参照されているRenderTextureの内容が変わっても動いていると判定されない

今回はCanvasのアップデートについて調べてみました。
今までの計測は、Unityエディタ上での実行時の値のため正確な検証にはなりません。
処理負荷を計測する場合は、端末で試すようにしてください。

# 参考URL
[フィルレート、Canvas、および入力 - Unity](https://unity3d.com/jp/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input?playlist=44987)