今回はuGUIのImageとSprite Rendererの使い分けについてメモします。
ImageとSprite Renderer
Unityで2Dの絵を簡単に出す方法として思いつくのが、Sprite Rendererのような2D機能を使用する方法と、uGUIのImage系の機能を使用する方法です。
どちらも「テクスチャを表示する」という振る舞いなので、画面に表示したようなケースでは、全く見分けが付きません。
![f:id:tsubaki_t1:20160924204917j:plain f:id:tsubaki_t1:20160924204917j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160924/20160924204917.jpg)
では、どちらを使っても良いのかと言われれば、まぁどちらを使っても良いのですが、有利な点・不利な点があります。
Sprite Rendererの振る舞い
Sprite Rendererは、それ単体でアセットを描画する機能を持ったレンダラーです。Sprite(アセット)に設定したポリゴン情報を元に、描画を行います。
描画順の制御にはSorting LayerとOrder In Layerを使用した単純な物になっており、座標もTransform準拠と、Unityの基本機能を少し拡張したような内容になっています。
![f:id:tsubaki_t1:20160925025332j:plain f:id:tsubaki_t1:20160925025332j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925025332.jpg)
Sprite Rendererを使用するには、2Dモード時にSpriteをScene Viewへドラッグ&ドロップするという簡単な方法で使用できます。
uGUI Imageの振る舞い
uGUIのImageは、CanvasRendererとCanvasとの組み合わせで描画する機能です。
Sprite(アセット)に指定したTextureとUV情報を元に、描画を行います。
描画の制御は、Hierarchyの順番に依存しますが、同時にCanvasに設定したSorting LayerとOrder in Layerによる描画順制御も行います。
座標はRectTransformと呼ばれるTransformを拡張したアセットが使用されます。サイズの変更はWidth/Heightにより行われ、Scaleを使用するとレイアウト機能が破綻する事が多いです。
![f:id:tsubaki_t1:20160925030900j:plain f:id:tsubaki_t1:20160925030900j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925030900.jpg)
uGUIのImageを使用したい場合、まずCanvasオブジェクト以下にImageを含むオブジェクトを作成し、Spriteを設定するといった手順になります。
最適化に関するアプローチの違い
最終的にはSpriteを表示するという点で似たような機能ですが、アプローチは全く異なっており、利用するケースが異なります。
![f:id:tsubaki_t1:20160925231634j:plain f:id:tsubaki_t1:20160925231634j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925231634.jpg)
SetPassの削減に関する振る舞いの比較
2Dにおいてパフォーマンスを稼ぎたい場合、一番目につく項目はSetPassの削減です。基本的にSetPassは少なければ少ないほど良いです。SetPassの数は、ProfilerのRendererやGameViewのStatsから確認出来ます。
![f:id:tsubaki_t1:20160925221327j:plain f:id:tsubaki_t1:20160925221327j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925221327.jpg)
Sprite RendererのSetpass削減ですが、基本的にDynamic BatchingとAtlas化に依存します。
この機能は他の3D描画機能の延長で作成されており、Atlasでマテリアルを切り替える回数を可能な限り削減し、Batchingでメッシュを纏めて一括描画を行います。
逆に、Atlasが異なるスプライトが描画順に挟まるとSetPassは増えます。
![f:id:tsubaki_t1:20160925222116j:plain f:id:tsubaki_t1:20160925222116j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925222116.jpg)
![f:id:tsubaki_t1:20160925224643j:plain f:id:tsubaki_t1:20160925224643j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925224643.jpg)
Canvas Imageの場合、実はAtlasが別でもSetPassは1です。
但しAtlas化が無意味という訳ではなく、一応Batchingの実行数に影響します。但し、パフォーマンスの影響的にはSetPassの方が重要なので、誤差の範囲です。
なおシーン内に複数のCanvas持つ場合、Canvasの数に応じてSetPassも増加します。また、UIのMaterialに他のマテリアルを設定すると、ソコは別に描画されるためかSetPassが増加します。
![f:id:tsubaki_t1:20160925230551j:plain f:id:tsubaki_t1:20160925230551j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925230551.jpg)
フィルレート・オーバードロー削減に関する振る舞いの比較
特に高解像度かつGPUが低スペックなモバイル環境下では、オーバードロー(何度も描画する)は非常に大きな問題になります。
また、Power VRを使用した端末(iPhone等)の場合はCutoutも苦手としているので、重なるようにモデルやUIを配置するのは、非常に無駄と言えます。
![f:id:tsubaki_t1:20160925231120j:plain f:id:tsubaki_t1:20160925231120j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925231120.jpg)
Sprite Rendererの場合、透明情報を元にポリゴンを作成します。テクスチャの解像度が高い場合は(キッチリくり抜く)ハイポリを、テクスチャ解像度が低い場合はローポリなメッシュを作成します。
透明部分をキッチリくり抜いてくれるのでオーバードローは削減出来ますが、代わりにポリゴンが少し増えます。
オプションとしてFullRect(ポリゴンでくり抜かない)を選択出来ますが、下の画像を見ての通り、オーバードローの範囲が増えやすくなります。 なお、この際にエディタで作成するポリゴンは調整出来ません。2D Experimentalでは可能なので、2D Experimental が統合されたら追加されそうです。
![f:id:tsubaki_t1:20160925232628j:plain f:id:tsubaki_t1:20160925232628j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925232628.jpg)
![f:id:tsubaki_t1:20160925232634j:plain f:id:tsubaki_t1:20160925232634j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160925/20160925232634.jpg)
uGUIのImageの場合、基本的に四角形で切り抜きます。
またSliceした際に中央を塗らないというオプションがあります。このポリゴンに穴を開ける処理は将来的にはSprite Rendererでも似たようなことが出来るようになると思いますが(2D Experimental で動作を確認出来る)、現状ではuGUIのみ使用できるみたいです。
![http://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20150324/20150324015719.png](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20150324/20150324015719.png)
またFullRectと異なり、透明の範囲をQUADでくり抜きます。
![f:id:tsubaki_t1:20160926010927j:plain f:id:tsubaki_t1:20160926010927j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160926/20160926010927.jpg)
大量に描画した時と動かした時の振る舞いの比較
uGUIのImageとSprite Rendererの最大の違いは、大量に描画した時と動かした時です。
まずSprite Rendererを利用して描画する場合、描画一つ一つに描画コストが発生するため、量が増えれば増えるほど描画負荷が上がります。
例えば256x256程度のスプライトを5000個程描画しようと思うと、自分のPCでは大体1.6~2.0msの描画コストが発生します。
ですが、Spriteを移動・変形・回転させた時のコストは、殆ど無いです。
![f:id:tsubaki_t1:20160926001454j:plain f:id:tsubaki_t1:20160926001454j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160926/20160926001454.jpg)
逆にuGUIのImageを描画する場合、動かさないならばCanvasに収集したCanvas Rendererの描画結果を使いまわすので、スプライトの個数が増えても描画コストは殆ど増えません。
Sprite Rendererと同じスプライトを1万2000個程度描画した場合でも、描画コストは0.2ms前後です。
![f:id:tsubaki_t1:20160926002306j:plain f:id:tsubaki_t1:20160926002306j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160926/20160926002306.jpg)
但し、Canvas以下のImageにに対し一つでも移動・変形・回転を行った場合、膨大な処理コストが発生します。
下画像の左の青い部分がソレで、5000個程度のImageのうち一つのImageをアニメーションさせると、大体71msという致命的なレベルの時間、処理が停止しました。
5個10個ならまだ無視しても良いのですが、もっと量が多いと非常に強力なボトルネックになりそうな予感があります。
この辺りはUnity 5.2で大分早くなりましたが、量が増えるとやはり問題になります。もっとコストを上げたければPixel Perfectにチェックをどうぞ。
![f:id:tsubaki_t1:20160926003013j:plain f:id:tsubaki_t1:20160926003013j:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/t/tsubaki_t1/20160926/20160926003013.jpg)
幸いなことに、動かすImageにCanvasを付けておく事で、動かした際のコストは激減します(上画像の右側)
但し、CanvasのEnable/Disableを切り替えると親Canvasが再構築を始めるので、そのあたりも注意です。(CanvasではなくImageをdisableする事で問題を最小に)
また、World Space / Screen Space Camera のUIの状態で、Canvasの座標・スケール・回転を操作した場合も再構築を始めるので、そっちも注意です。
つまり、どのように使い分けるべきか
つまるところ、
- uGUIは動かすことを余り想定していない
動かさなければ、低負荷 低消費電力
- uGUIはCanvas単位で動かす
- Spriteは動かすことを前提
- 沢山描画する場合に対して最適化
といった方針が見えます。
また実際には
- 動かさないがα0の範囲が大きいならSprite Renderer
(但し、矩形できれいに抜ける場合はその限りではない)
- スプライトを常に大量に動かすならSprite Renderer
- オーバードローが多いならSprite Renderer
- 画像を絶対に動かさないならuGUIのImage
- 小さいスプライトを一部動かすならuGUIのImage
(動かすImageは別途Canvas必須)
といった感じで使い分けるのが良さそうです。
この感じだと、実はSprite RendererでUI作ったり、ImageでSTG作るのも実はアリなんじゃないかなって思わなくもなくもなくも。(個人的な感想
Tips
- Sprite RenderのレイヤーはSpriteRendererを設定したオブジェクト
ImageのレイヤーはCanvasを設定したオブジェクト
- CanvasにImageを設定すると、CanvasのサイズにフィットしたImageが作られる。World Spaceでとりあえず壁に絵を貼るときに便利
- Canvas内のImageの一部でもCameraに写っているとCanvas内の全Imageが描画される。カリングはCanvas単位
- 忘れがちだが、CanvasにSorting LayerとOrder In Layerが付いてる
関連
tsubakit1.hateblo.jp
tsubakit1.hateblo.jp