Skip to main content
Unityでパフォーマンスの
良いUIを作るためのTips
Tatsuhiko Yamamura @ Unity
ver 1.1
お品書き
• UnityのUIシステムおさらい
• パフォーマンスの良いUIの為に注意すべきこと
• バッチング
• フィルレート
• リビルド
• その他
Unity UI
Unity UI
• Unity UI、通称UGUI
• Unity 4.6のタイミングで追加
• オープンソースで開発

https://bitbucket.org/Unity-Technologies/ui/
• 当時はNGUIというアセット(有料)を殆どの人が使用していた
• NGUI開発者が中の人になって一緒に開発
uGUIの機能
• Text
• Image
• Raw Image
• Button
• Toggle
• Slider
• etc
• Basic Layout
• Layout Group
• etc
ビジュアル表現 インタラクション レイアウト
uGUIには結構いろいろな機能がある
UGUIの機能
• 基本的なレイアウト
• 自動レイアウト
• ビジュアル表現
• インタラクション
UGUIの機能
• 基本的なレイアウト
• 自動レイアウト
• ビジュアル表現
• インタラクション
UGUIの機能
• 基本的なレイアウト
• 自動レイアウト
• ビジュアル表現
• インタラクション
https://github.com/mob-sakai/UIEffect
UGUIの機能
• 基本的なレイアウト
• 自動レイアウト
• ビジュアル表現
• インタラクション
全自動

UI最適化ボタン
結構、なんとなくで作れてしまうUIシステム、
ただしパフォーマンスも担保してくれるかというと、微妙にしてくれない。
全自動最適化ボタンは無い。
パフォーマンス柔軟性
柔軟性とパフォーマンスはトレードオフ
実際に標準の機能を使用せず、自作する必要も出てくる
バッチング
描画処理
サンプル キャラクター
サンプル キャラクター
サンプル キャラクター
描画は奥から順にスタンプのように貼り付けていくイメージ
描画する流れ
Texture
Material (+Shader)
Polygon 描画
描画は基本のポリゴンと同じく、マテリアルでポリゴンを塗る方式
複数繰り返す
描画処理をたくさん繰り返すのは効率が悪い
Canvasに一旦まとめる
Canvas
マテリアル
テクスチャ
UIのレイアウト 結合したポリゴン
平面上の要素なので、多少楽をしている(らしい) ※まとめるのはHierarchyではなく実際の描画ベース
まとめて一回で描画(バッチング)
まとめたポリゴンを一気に描画すれば、大量に描画しても負荷はソコソコ
一括描画できるのは

同じテクスチャのみ
異なるマテリアルの場合、異なるポリゴンを用意して描画(バッチが解ける)
入れ子のせいで

バッチングできない
スプライトが入れ子になるとバッチング出来なくなる ※Hierarchyではなく描画ベースの入れ子
入れ子のせいで

バッチングできない
テクスチャが同じでもマテリアルが異なるとバッチング出来ない
バッチが可能な条件
• 同じCanvasに所属している
• 同じMaterialを使用している
• 同じTextureを参照している
• 座標のZ値が同じ位置にあること
• 同じマスクでクリップされていること(RectMask2Dの場合)
バッチングの為には全ての条件を満たす必要がある
UI Profilerで観察
確認
方法バッチングが解けてるかどうかはUI Profilerで確認 (Unity 2017.1から)
バッチ一覧
バッチで

まとめた

UI
描画一覧と、バッチでまとめたUIの一覧を確認できる
バッチ出来ない理由
テクスチャが違う
バッチが解ける理由も表示される
Sprite Atlas
対策
バッチをまとめたい場合はSpriteAtlasにまとめる
参照するテクスチャが

同じなので、バッチングが出来る
Sprite Atlasで

一つのテクスチャにまとめる
UVの異なる矩形ポリゴン
一枚のテクスチャにまとめれば、同じマテリアルで複数のUIを表示出来る
違うタイプのキャラクターが

混ざっても1回で描画できている
実際の描画の様子
元画像
なにか違う…
右はオリジナル、左はSpriteAtlasで取得したスプライト。妙な模様が追加されている
スプライトをミッチリと詰めた為に発生。UIは基本的に矩形なので、ミッチリ詰めると発生
余裕を持って

配置する
対策
対策1:Tight Packingを止めて、余裕を持って配置
Imageを矩形ではなく

ポリゴンで描画する

(Unity 2018.3から)
対策
対策2:矩形ではなくポリゴンを使用して描画
バッチング対策
• バッチの条件を満たす
• テクスチャをパックする
• マスクの使用は抑える
対策
フィルレート
ポリゴンに色を塗る際は

1ピクセルずつ塗っていく
ポリゴンを表示し、色を画面(スクリーン)の1ピクセルずつ塗っていく。これをマテリアル単位でGPUで一括で行う
沢山のUIがある場合
重なって表示している場合に問題になる。
沢山のUIがある場合
複数回塗られている
透明は「描画しない」ではなく「透明を塗る」なので、重なっている部分が複数回塗られる(オーバードロー)
UI Profiler

(Composite Overdraw)
確認
方法UI ProfilerのCompsiteOverdrawで確認できる。色が赤に濃いほど負荷が高い
UI Profilerで確認しているの図
オーバードロー対策
• 不要なUIを排除
• 重なる部分を出来るだけ削る
• Sprite Meshを使用
• Fill Centerを外して、中央をくり抜く
• オーバードローしても問題ないシェーダーを使う
sprite mesh使用 通常のquad
対策
• シェーダーの負荷は

各デバイスのプロファイラーで
確認する
• XCodeはいいぞ
UIのリビルド

(ジオメトリ)
Canvasに一旦まとめる
Canvas
マテリアル
テクスチャ
UIのレイアウト バッファ
前述の通り、Canvasは一旦描画するUIのポリゴンをまとめて、バッファを生成。 バッファは基本使い回される
2300個のImage
殆ど負荷がかからない
単一描画になるので効率よく描画できる。バッチング出来ているなら殆どCPUに負荷がかからない(iPadPro1stで検証)
変化があれば再構築が必要
Canvas
マテリアル
テクスチャ
UIのレイアウト バッファバッファ
更新
更新
更新
UIを動かすと以前のバッファを使い回せなくなるので、バッファを作り直す必要がある
バッファの再構築は即時ではない
Update LateUpdate Post Rate Update
SendWillRenderCanvasesSetDirty
変化した内容に応じて
Dirtyフラグを付ける
UIを再構築
Canvas
バッファは即時反映ではなく、変化した物にDirtyフラグをセット、溜めて一括処理する
再構築の条件
• UIをenable/disable
• マテリアルが変化
• RectTransformのサイズが変化
• Transformの親が変化
UI再構築の条件は結構緩く、簡単に再構築が走る
一つ変わると、同じCanvasの全ての
UIを再構築する
Set Dirty
同じCanvasが全て

更新対象に
ジオメトリはCanvas単位で管理しているので、一つでも変更があれば同Canvas全てに影響する
このUIのどれかが動けば、背景のUIにリビルドが走る
メインスレッドの

処理は少ない
Canvas内のサイズによってジオメトリの再構築に時間がかかる
配置により

ソートに時間がかかる
ジオメトリの再構築はメインスレッドではない為、即座にフレームが落ちる訳ではない(テキスト…フォントの取得は除く)。ただし

ジオメトリ生成完了までレンダースレッドが遅延するので、量によっては固まる。また、他のジョブ進行にも影響する。
確認
方法
UI Profilerが動いている
動いていなければ静か
確認方法
UI Profilerで確認出来る
Profilerでも分かりやすい
確認
方法
通常のプロファイラーでもUGUIRenderingが伸びるので確認できる
CPU Profiler(Hierarchy)で
OnDidApplyAnimationPropertiesや
OnRectTransformDimensionsChangeで検索 追加表示項目を変更

Show Related Objects(関連オブジェクトを表示)
UI再構築してそうなの一覧が表示される
確認
方法
UIのEventSystemでUIが動いている場合、誰がUIを動かしたのかを確認できる
Canvasを分割
対策
対策1:SubCanvasを作りジオメトリを分割する
Set Dirty
Static Canvas
Dynamic Canvas
違うCanvasなのでリビルド対象外
Canvasを分割
Canvasを分割することで、UIを動かしてもジオメトリの再構築範囲を限定できる
(Canvasのジョブ完了待ちが

消えた効果もある)
Canvas分割の効果。UIのソートやジオメトリの生成コストが殆ど無くなる
動くUIはImageのSprite Meshを避ける
use sprite mesh : enable
3.22ms
use sprite mesh : disable
0.4ms対策
SpriteMeshはポリゴンが多く、ジオメトリ生成のコストが上がる。動かすUIには使わない方が無難
親UIを動かす
element
element
element
element
element
element
element
root
親Transformを動かした場合もUIの再構築が走る。 ScreenSpaceCameraを使用している場合、よく起こる
動かしたタイミングで負荷
確認
方法
UIProfilerでRenderの山ができている場合はコレ
• 要素数を減らす
• 要素をプーリングして

最小限のUIでどうにかする
対策
コレといった対策は無い。 動くCanvasには要素はあまり突っ込まないのが良い
UIのリビルド

(Layout Group)
RectTransform
子の
RectTransform
子のRectTransform
子
の
RectTransform 子の
RectTransform
LayoutGroupは、例えばRectTransformの中に大量のUIがある時、
RectTransform

ILayoutGroup
子のRectTransform
子のRectTransform
子のRectTransform
子のRectTransform
ILayoutGroupで

子RectTransformの情報を収集し
子オブジェクトを自動的に並べてくれる(レイアウトする)機能
RectTransform

ILayoutGroup
ILayoutSelfController
子のRectTransform
子のRectTransform
子のRectTransform
子のRectTransform
ILayoutSelfControllerで

子RectTransformの情報を元に

自分のオブジェクトのサイズを変更する
子オブジェクトの情報を元に、親(自身)のサイズを調整することも可能
RectTransform

ILayoutGroup
ILayoutSelfController
子のRectTransform
子のRectTransform
子のRectTransform
子のRectTransform
要素が増えても

自動的に対応子のRectTransform
子のRectTransform
要素が増えたりサイズが変わっても対応できる、柔軟性を持つ
うまく組み合わせれば、横に伸びて縦に要素を加算するUI…みたいなものも作れる
特にScroll Viewとかでよく使われる機能
Dirty System
Layout 

Group
Element
Element
Element
Element
UI LayoutもDirtySystemを使用して実現している
Dirty System
Layout

Group
Element
Element
Child
Child
DirtyElement
UIの変更があればDirty (この例では緑のElementのサイズを変更)
Child
Dirty System
Layout

Group
Element
Element
Element
リビルド申請
DirtyしたUIから親LayoutGroupへ、レイアウトの再構築を申請
Element
Dirty System
Layout

Group
Element
Element
Element
再レイアウト
あとで一斉にUIを再レイアウトする
Dirty System
Layout

Group
Element
Element
Element
Layout

Group
Layout

Group
Element
Element
Element
リビルド申請
Element
複数のLayoutGroupがある場合は
Dirty System
Layout

Group
Element
Element
Element
Layout

Group
Layout

Group
Element
Element
ElementElement
LayoutGroupの親まで到達して、そこから再レイアウト
(なので、途中で自身のサイズを変えるContentSizeFitterはLayoutGroupの間に挟むと具合が悪い)
Groupの更新
Layout

Group
Element
Element
Element
Layout

Group
Layout

Group
Element
Element
ElementElement
parent.GetComponents()
parent.GetComponents()
rect.GetComponents()
この親LayoutGroupへの通知は、毎回GetComponentを使用。子のレイアウト変更にもGetComponentを使用。

地味に負荷になる
DirtyするUIが紛れ込むだけで

毎フレーム、UIのリビルドが走る
一つでもDirtyするUIが含まれると、毎回Layoutの再構築。 これはメインスレッドで実行され、妙に高い負荷になる
UI Profilerが動いている
動いていなければ静か
確認方法
確認
方法
再レイアウトが起こっている場合、UI ProfilerのLayoutが大きく変化する
メインスレッドで処理されるため、大きな負荷と認識される事が多いです。
再レイアウト処理
• RectTransformに関連するものを操作しない

(結果は変化しなくとも、セットするだけでリビルドが走る)
• Layout Propertyに関連するものを操作しない
• UI Element関連を操作しない
• Mecanimのステートマシンに、上記の要素にアクセスする

AnimationClipを入れない

(Simple Animationなら多分OK)
対策
対策
もしUIのインタラクティブな機能(ScrollViewとかButtonとか)から呼び出している場合、ProfilerのUI Detailで

誰が呼び出しているのか確認できる
LayoutGroupを

可能な限り使わない
対策
その他
TextコンポーネントのTextを変更する際にメインスレッドでフォント取得処理が発生 (※変更しなければ発生しない)

Textコンポーネント1つなら問題無いが、大量にあると負荷になる
500個のUI.Textコンポーネントのtextを変更
その他
Font.CacheFontForText
その他
• Raycast Targetは可能な限り外す
• World Space CameraのEvent Cameraにはカメラを登録する

(無い場合はFindTag(“MainCamera”)を呼ばれる
• Pixel Perfect設定は動かないカメラのみ設定する
• テクスチャは画面解像度に合ったものを使う、

可能な限り圧縮する
その他
• UI Profilerはエディターのみ動作
• XCode等のデバイス専用プロファイラーは

詳しい情報を収集できて便利
• 負荷を見るならビルドする

Windows向けでも、多くのノイズを取り除いてくれる

(実機で確認するのがベターではある)
まとめ
ポイント
• 動かさなければ負荷は低い
• 動かすなら最低限の影響に抑える
• ドローコールとフィルレート、バッチング。 良いバランスを取る
•気を抜くな!誰も信用するな!
プロファイラーを手放すな!
UI Element(Runtime)
Coming Soon
おしまい