※ 17/05/19 時点で公式が日本語に対応されていたので、今後必要な方は公式サイトをご参照ください。
この投稿は Unity 2 Advent Calendar 2016 の 2日目の記事です。
Unity UI について学習する機会があった為、 Unity - Best Practices にある Optimizing Unity UI の一部を翻訳(意訳)してみました。
具体的には下記2ページになります。
ご注意
- Unity 初心者が学習がてら翻訳した為、意味が誤っている箇所が多数あるかもしれません。
- 誤り箇所はご指摘頂けますと大変助かります。
Unity UI の最適化ガイド
原文: A guide to optimizing Unity UI
確認済のバージョン: 5.3 - 難易度: 上級
Unity UI によって実装されたユーザーインターフェイスの最適化はもはや芸術です。厳しいルールは殆どありません。その代わり、システムの挙動を念頭に置いて、各状況を慎重に判断する必要があります。Unity UI を最適化する際の中核となるのは、ドローコールとバッチ処理のバランスをとる事です。一方または両方を減らす為には一般的なテクニックを活用する事が出来ますが、複雑な UI の場合はトレードオフを行わなければなりません。
とはいえ、他トピックでのベストプラクティスと同様に、Unity UI を最適化する際もプロファイリングから始めるべきです。Unity UI システムを最適化しようとする際は、まず現在発生している問題の正確な原因を突き止めることが必要です。Unity UI の利用者が遭遇する問題には、共通する次の4つがあります。
- 過大な GPUフラグメントシェーダの利用率 (=フィルレート制限超え)
- 過大な Canvas バッチのリビルドに費やされたCPU時間
- 過大な Canvas バッチのリビルド数 (頻繁なキャッシュ更新)
- 過大な 頂点生成に費やされたCPU時間 (大抵の原因はテキスト)
原則として、GPUへのドローコール数によってパフォーマンスが低下しても、Unity UI を生成する事は可能です。とはいえ、実際にはドローコールが増えGPUに過負荷が掛かるプロジェクトの場合、フィルレート制限を超えてしまう可能性が高くなります。
このガイドでは、Unity UI の根底にある基本的な概念・アルゴリズム・コード、また一般的な問題や解決策について、5つの章に分けて説明します。
-
Unity UI の基礎 ... Unity UI 固有の用語を定義し、UI をレンダリングするために実行される基本的なプロセス(バッチジオメトリの構築など)の詳細について説明
します。 この章から読み始める事を強くオススメします。 - Unity UI のプロファイリングツール ... 開発者が利用できる様々なツールを使ってプロファイリングデータを収集する方法について説明します。
- フィルレート、Canvases、入力 ... Unity UI の Canvas と入力コンポーネントのパフォーマンスを向上させる方法について説明します。
- UI コントロールの最適化 ... これまでの項目で紹介できなかったテクニックと一緒に、UIテキスト、スクロールビュー、他のコンポーネント固有の最適化について説明します。
-
その他のUI最適化のテクニックとヒント ... UI システムでの「落とし穴」に関する基本的なヒントと回避策、これまでの章で説明出来なかっ
た問題について説明します。
UI ソースコード
Unity UI のグラフィックとレイアウトコンポーネントはオープンソースであることを常に忘れないでください。 それらは Unity’s Bitbucket repository の [UI](https://bitbucket.org/Unity-Technologie
s/ui/) にて確認する事が出来ます。
Unity UI の基礎
確認済のバージョン: 5.3 - 難易度: 上級
重要なのは Unity UI システムを構成する様々な部品を理解する事です。その基本的な部品(クラスやコンポーネント)が組み合わさりシステムを構成しています。この章では先ず一連の記事を通して使用される用語を定義し、Unity UI のキーとなるシステムについて、低レベル層での動作について説明します。
用語
Canvas ... ネイティブコードの Unity コンポーネントで、 Unity の描画システムで階層化されたジオメトリを提供する為に使用されます。そしてゲーム空間の中、又は前面に描画されます。
Canvases は、それらの構成ジオメトリをバッチに結合する責任があり、適切な描画命令を生成して Unity の描画システムに送信します。これらは全てネイティブのC++コードで行なわれ、リバッチ、または バッチビルド と呼ばれます。Canvas に再配置が必要なジオメトリが含まれているとマークされている時、その Canvas は ダーティ であると表現します。
ジオメトリは Canvas Renderer components によって、 Canvas に提供されます。
Sub-canvas ... 単純に Canvas コンポーネント内にネストされた別の Canvas コンポーネントの事を表します。Sub-canvas は 親Canvas とは分離されているため、ダーティな Sub-canvas は親Canvas にジオメトリを再構築させることはありません。逆もまた同様です。
(※ 親Canvas を変更すると Sub-canvas のサイズが変更されるなど、これが当てはまらない場合もあります。)
Graphic ... Unity UI C#ライブラリによって提供されるクラスであり、Canvas システムに描画可能なジオメトリを提供する全ての Unity UI C# クラスの基底クラスになっています。初めから準備されている Unity UI Graphics の殆どは、 MaskableGraphic のサブクラスを介して実装されています。このサブクラスを使用すると、IMaskable インターフェイスを介してマスクすることができます。Drawable の主なサブクラスとして Image と Text があり、これらのクラスは同名のコンポーネントを提供します。
Layout components ... RectTransform のサイズと位置制御を担っており、相対的なサイズ変更・位置変更を必要とする複雑なレイアウトを作成する為に使用されます。 Layout components は RectTransforms のみに依存し、関連するプロパティにのみ影響を与えます。それらは Graphic クラスに依存せず、Unity UI の Graphic コンポーネントとは独立して使用できます。
Graphic と Layout components の両方が、Unity Editor のインターフェイスで公開されていない CanvasUpdateRegistry クラスに依存しています。このクラスは更新が必要な Layoutコンポーネント と Graphicコンポーネントのセットを追跡し、関連付けられた Canvas が willRenderCanvases イベントを呼び出す時に、必要に応じて更新をトリガーします。
Layout および Graphic コンポーネントの更新は、リビルド と呼ばれます。 リビルド プロセスについては、このドキュメントの後半でさらに詳しく説明します。
レンダリング詳細
Unity UI でユーザーインターフェイスを作成する際は、Canvas で描画されたジオメトリが全て透過キューに描画されることに注意してください。つまり、Unity UI によって生成されたジオメトリは、常にアルファブレンドが行われ奥から手前に順番に描画されます。パフォーマンスの観点から覚えておくべき重要なことは、ポリゴンからラスタライズされた各ピクセルが、他の不透明なポリゴンで完全に覆われていてもサンプリングされることです。モバイルデバイスでは、この高いレベルのオーバードローはGPUのフィルレート容量を急速に上回る可能性があります。
バッチビルドプロセス(Canvases)
バッチビルドプロセスとは、 Canvas がUI要素を表すメッシュを結合し Unity のグラフィックスパイプラインに送る適切なレンダリングコマンドを生成するプロセスです。この結果はキャッシュされ、ダーティであるとマークされるまで再利用されます。これは構成するメッシュの1つにでも変更がある度に発生します。
Canvas で使用されるメッシュは、Canvas にセットされた Canvas Renderer components のセットから取得されますが、 Sub-canvas は含まれません。
バッチを計算するにはメッシュを深さ(depth)でソートし、重なりや共有されているマテリアルなどを調べる必要があります。この処理はマルチスレッドであるため、CPUアーキテクチャによってパフォーマンスが大きく異なります。特にモバイルSoC(一般的にCPUコア数が少ない)と最新のデスクトップCPU(4コア以上の場合が多い)との間には大きな違いがあります。
リビルドプロセス(Graphics)
リビルドプロセスでは、Unity UI のC#グラフィックコンポーネントの Layout とメッシュが再計算されます。これは CanvasUpdateRegistry クラスで実行されます。これはC#クラスであり、そのソースは Unity’s Bitbucket で公開されています。
具体的には CanvasUpdateRegistry クラスの PerformUpdate メソッドが担っており、このメソッドは Canvas コンポーネントが WillRenderCanvases イベントを発生させるたびに呼び出されます。このイベントは1フレームにつき1回呼び出されます。
PerformUpdate メソッドは3つのプロセスを実行します。
- ダーティな Layout コンポーネントは、レイアウトのリビルドを ICanvasElement.Rebuild メソッドを通して要求されます。
- 登録されたクリッピングコンポーネント(Mask など)は、クリップされたコンポーネントを全て取り除くする様に要求されます。これは ClippingRegistry.Cull 1 メソッドによって行われます。
- ダーティな Graphic コンポーネントは、グラフィカル要素のリビルドを要求されます。
Layout 及び Graphic のリビルドの場合、プロセスは複数のパーツに分割されます。Layout のリビルドは3つ(PreLayout, Layout, PostLayout)で実行され、Graphic のリビルドは2つ(PreRender, LatePreRender)で実行されます。
Layout の リビルド
Layout コンポーネントに含まれるコンポーネントの適切な位置・サイズを再計算するには、適切な階層順に Layout を適用する必要があります。GameObject 階層のルートに近い Layout は、自身が含んでいる Layout の位置とサイズを変更する場合がある為最初に計算する必要があります。
これを行う為に、Unity UI はダーティな Layout コンポーネントのリストを、階層の深さでソートします。階層の上位にあるアイテム(親の Transform コンポーネントが少ない)は、リストの先頭に移動されます。
次に、ソートされた Layout コンポーネントリストは Layout をリビルドするよう要求されます。ここは、 Layout コンポーネントによって制御されているUI要素の位置とサイズが実際に変更される場所です。個々の要素の位置が Layout によってどの様に影響を受けているかについては UI Auto Layout を参照してください。
Graphic のリビルド
Graphic コンポーネントがリビルドされると、 Unity UI は ICanvasElement インターフェイス の Rebuild メソッドに制御を渡します。Graphic クラスはこれを実装し、リビルドプロセスの PreRender ステージで2つの異なるリビルドステップを実行します。
- 頂点データがダーティとマークされていた場合、メッシュがリビルドされます。(例.コンポーネントの RectTransform のサイズが変更された時)
- マテリアルデータがダーティとマークされている場合、アタッチされた Canvas Renderer のマテリアルが更新されます。(例.コンポーネントのマテリアルorテクスチャが変更された時)
Graphic のリビルドは、Graphic コンポーネントのリストを特定の順序で処理しない為、ソート操作を必要としません。
終わりに
Optimizing Unity UI は全6章で構成されている為、本来は全章翻訳したかったのですが
実際に翻訳をしてみて中々骨の折れる作業だなと痛感しました。
(いつの間にか公式さんが対応されそう)
明日は昨日に引き続き YuukiOgino さんによる 「10日で学ぶ、Unity5 2Dゲーム入門で掲載されたスクリプトをビジュアルスクリプト(Nottorus)でやってみる」 です。お楽しみに!
-
クラス名が間違っている様で、正しくは
ClipperRegistry.Cull
↩