uGUIでオーバードローを減らそう

本記事は、サムザップ [Advent Calendar 2019 #1](https://qiita.com/advent-calendar/2019/sumzap1) の12/22の記事です。

# はじめに
ゲームのUIって画像が何枚も重なり合って動作が重くなってしまう現象が発生しがち。
そんなUIのオーバードローを減らしてゲームの動作を軽くしようというお話です。

# サンプルUI
こんなUIがあったとしましょう
(無理やり重ねた感)
![GameView-s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/50c0bc61-c0b8-2d99-2a9f-5064b7062075.png)

### オーバードロー
このUIのオーバードローを見てみると…
![Overdraw1-s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/326c83c8-3fec-a5c6-feef-6577d27ffa9c.png)
色が明るくなっているところが何枚も重なり合っているところです。
これはUnityのSceneViewで左上のプルダウンからOverdrawを選択すると表示されます。

### 描画範囲
斜めになっている四角い画像は色がついている部分だけじゃなく全体が描画されてしまいます。
(透明だから見えないだけ)
![Sprite-View-s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/7bd5daab-97c3-da51-627d-396548b55825.png)
透明で見えない部分まで描画されてしまうなんてもったいない。

# 透明部分を描画しない方法
実は簡単にできます。
画像とImageのInspectorを設定するだけ。

画像のInspectorはMeshTypeをTightにするだけ！
![Inspector-Sprite.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/ca5b1d86-cf8e-e057-13c2-ea87fca82eb8.png)
ImageのInspectorはUseSpriteMeshにチェックを入れるだけ！
![Inspector-Image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/9301a015-93b9-ba07-0c39-0a64d6156beb.png)

そうすると描画されていない部分のオーバードローが消えました！
![Overdraw2-s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/710a9e9b-dd42-da46-4af2-43eae9bca1e3.png)

# 効果
問題はこれをやることがどれほど効果があるのかってことですよね。
この程度のオーバードローだとあんまり変わらないのでもっと重ねてみたいと思います。

### Use Sprite Mesh チェックなし
![Test1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/29b8275a-75a2-8126-998b-602dd65405df.png)
適当な三角形の画像を作成して画面内に3000枚表示してみました。
FPSも表示してみましたが約30fpsぐらいでした。

### Use Sprite Mesh チェックあり
![Test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/cd0dcc8a-cdc8-cc21-2a77-e0e24a7ee32f.png)
約50fpsでした。
※検証にはAndroid端末(GalaxyS9)を使用しました。

### サンプルコード
```Test.cs
public class Test : MonoBehaviour
{
    [SerializeField] RectTransform rectTransform = null;
    [SerializeField] GameObject goImage = null;
    [SerializeField] Text txtFps = null;
    int frames;
    float prevTime;

    // Start is called before the first frame update
    void Start()
    {
        float width = rectTransform.rect.width;
        float height = rectTransform.rect.height;
        for( int i = 0; i < 3000; i++ )
        {
            GameObject go = Instantiate( goImage, rectTransform );
            go.SetActive( true );
            go.transform.localPosition = new Vector2(
                Random.Range( -width * 0.5f, width * 0.5f ),
                Random.Range( -height * 0.5f, height * 0.5f )
            );
        }
        frames = 0;
        prevTime = Time.realtimeSinceStartup;
    }

    // Update is called once per frame
    void Update()
    {
        ++frames;
        float time = Time.realtimeSinceStartup - prevTime;
        if( time >= 0.5f )
        {
            txtFps.text = string.Format( "FPS:{0:f1}", frames / time );
            frames = 0;
            prevTime = Time.realtimeSinceStartup;
        }
    }
}
```

# 仕組みの解説
普通に描画すると四角形で描画されてしまうのにどうやって色の付いてる部分だけを描画しているのかを解説します。
といっても解説するほどのことはなく画像をMeshとして切り出しています。
その証拠にGameViewでStatusを見てみると
![Test1-Status.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/76d019b4-e490-35c1-fd0b-1edced7669ee.png)
3000枚の画像をUseSpriteMeshにチェックを入れず描画した方はVertsが12.0k(1万2千頂点)なのに対し
UseSpriteMeshにチェックを入れた方は117.0k(11万7千頂点)となっています。
![Test2-Status.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543556/5159b3e9-2ed1-bc29-65fd-198df5e26a7a.png)

# まとめ
頂点数が増えてもオーバードローが減れば動作は軽くなる！

それでは明日は@kotaroyさんの記事です。
お楽しみに！