バージョンチェック:2017.3 -Difficulty:上級
この章では、UnityUIの構築に関するより広範な問題について説明します。
フィルレートの問題の修正
GPUフラグメンテーションパイプラインへのプレッシャーを軽減するために、次の2つのアクションを実行できます。
- フラグメントシェーダーの複雑さを軽減します。詳細については、「UIシェーダーと低スペックデバイス」のセクションを参照してください。
- サンプリングする必要のあるピクセル数を減らします。
UIシェーダーは通常標準化されているため、最も一般的な問題は、フィルレートの過度の使用です。これの最も一般的な理由は、画面の重要な部分を占める多数の重複するUI要素および/または複数のUI要素です。これらの問題は両方とも、非常に高いレベルの当座貸越につながる可能性があります。
充填率の過剰使用を減らし、当座貸越を減らすために、以下の可能な救済策を検討してください。
目に見えないUIを排除する
既存のUI要素を再設計する必要性が最も少ないのは、プレーヤーに表示されない要素を単に無効にすることです。これの最も一般的なケースは、不透明な背景でフルスクリーンUIを開くことです。この場合、フルスクリーンUIの下に配置されたUI要素を無効にすることができます。
最も簡単な方法は、非表示のUI要素を含むルートGameObjectまたはGameObjectsを無効にすることです。代替ソリューションについては、「キャンバスの無効化」セクションを参照してください。
最後に、UI要素のアルファを0に設定して、UI要素が非表示になっていないことを確認します。要素は引き続きGPUに送信され、貴重なレンダリング時間がかかる可能性があるためです。UI要素がグラフィックコンポーネントを必要としない場合は、それを削除するだけで、レイキャスティングは引き続き機能します。
UI構造を簡素化する
UIの再構築と表示に必要な時間を短縮するには、UIオブジェクトの数を可能な限り減らす必要があります。できるだけ焼きます。たとえば、混合GameObjectを使用して色調を要素に変更するだけでなく、マテリアルプロパティを介してこれを実現します。また、シーンを整理する以外の目的がないフォルダなどのゲームオブジェクトを作成しないでください。
非表示のカメラ出力を無効にする
背景が不透明なフルスクリーンのユーザーインターフェイスを開いた場合でも、ワールドスペースカメラはUIの背後に標準の3Dシーンを表示します。レンダラーは、フルスクリーンのUnityユーザーインターフェイスが3Dシーン全体をぼかすことを知りません。
したがって、完全にフルスクリーンのユーザーインターフェイスがオンになっている場合、ぼやけたワールドスペースカメラをすべて無効にすると、GPUへの負荷が軽減され、無駄な作業が排除され、3Dワールドがレンダリングされます。
UIが3Dシーン全体をカバーしていない場合は、シーンを1回レンダリングして、常に表示するのではなく使用することをお勧めします。3Dシーンでアニメーションコンテンツが表示される可能性は失われますが、ほとんどの場合、これは許容できるはずです。
注:キャンバスが「画面スペース-オーバーレイ」に設定されている場合、シーン内のアクティブなカメラの数に関係なく描画されます。
大多数が隠されたカメラ
多くの「フルスクリーン」UIは、実際には3Dの世界全体をカバーしていませんが、世界のごく一部を表示します。このような場合、レンダリングされたテクスチャに表示されている世界の一部のみをキャプチャする方が理想的です。ワールドの表示部分がレンダリングテクスチャに「キャッシュ」されている場合、実際のワールドスペースカメラを無効にし、キャッシュされたレンダリングテクスチャをUI画面の背後に描画して、3Dワールドの実例となるバージョンを提供できます。
構成ベースのUI
デザイナーが標準の背景と要素を組み合わせてレイヤー化して最終的なUIを作成することにより、UIを作成することは非常に一般的です。これは比較的単純で反復に非常に適していますが、UnityUIは透過的なレンダリングキューを使用するため、これはパフォーマンスと一致しません。
背景、ボタン、およびボタン上のテキストを含む単純なUIについて考えてみます。透明度キュー内のオブジェクトは後ろから前に並べ替えられるため、ピクセルがテキストグリフに分類されると、GPUは背景のテクスチャ、ボタンのテクスチャ、最後にテキストアトラスのテクスチャをサンプリングする必要があります。合計3つのサンプル。UIの複雑さが増し、より多くの装飾要素が背景に重ねられると、サンプルの数が急速に増加します。
大きなUIが塗りつぶし率によって制限されていることがわかった場合、最良の方法は、専用のUIスプライトを作成し、UIの多くの装飾的/不変の要素を背景テクスチャに組み込むことです。これにより、目的のデザインを実現するために重ねる必要のある要素の数が減りますが、手間がかかり、プロジェクトのテクスチャアトラスのサイズが大きくなります。
特定のUIを作成するために必要な階層化された要素の数を専用のUIスプライトに凝縮するという原則は、子要素にも使用できます。製品のスクロールペインを備えたストアUIについて考えてみます。各製品のUI要素には、境界線、背景、および価格、名前、その他の情報を示すいくつかのアイコンがあります。
ストレージUIには背景が必要ですが、その製品はバックグラウンドでスクロールしているため、製品要素をストレージUIの背景テクスチャにマージすることはできません。ただし、商品のUI要素の境界線、価格、名前、およびその他の要素は、商品の背景に組み込むことができます。アイコンのサイズと数によっては、広告掲載率を大幅に節約できます。
階層化された要素を組み合わせるには、いくつかの欠点があります。プロフェッショナルな要素は再利用できなくなり、作成するには追加のアーティストリソースが必要になります。大きな新しいテクスチャを追加すると、特にUIテクスチャがオンデマンドでロードおよびアンロードされない場合、UIテクスチャに対応するために必要なメモリ量が大幅に増加する可能性があります。
UIシェーダーと低スペックデバイス
Unityユーザーインターフェイスで使用される組み込みのシェーダーには、マスキング、トリミング、およびその他の多くの複雑な操作のサポートが含まれています。この複雑さが増すため、UIシェーダーのパフォーマンスは、iPhone4などのローエンドデバイスの単純なUnity2Dシェーダーほど良くありません。
ローエンドデバイスのアプリケーションがマスキング、トリミング、およびその他の「派手な」機能を必要としない場合は、次のような最小限のUIシェーダーなど、未使用の操作を省略するカスタムシェーダーを作成できます。
Shader "UI/Fast-Default"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityUI.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
half2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
};
fixed4 _Color;
fixed4 _TextureSampleAdd;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.worldPosition = IN.vertex;
OUT.vertex = mul(UNITY_MATRIX_MVP, OUT.worldPosition);
OUT.texcoord = IN.texcoord;
#ifdef UNITY_HALF_TEXEL_OFFSET
OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
#endif
OUT.color = IN.color * _Color;
return OUT;
}
sampler2D _MainTex;
fixed4 frag(v2f IN) : SV_Target
{
return (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
}
ENDCG
}
}
}
UIキャンバスが再構築されます
UIを表示するには、UIシステムは、画面に表示される各UIコンポーネントのジオメトリを構築する必要があります。これには、動的レイアウトコードの実行、UIテキスト文字列内の文字を表すポリゴンの生成、および可能な限り多くのジオメトリック図形の単一のInへのマージが含まれます。グリッド、描画呼び出しを最小限に抑えます。これは複数のステップからなるプロセスであり、このガイドの冒頭で詳しく説明されています。
キャンバスの再構築は、次の2つの主な理由により、パフォーマンスの問題になる可能性があります。
- キャンバス上の描画可能なUI要素の数が多い場合、計算バッチ処理自体が非常に高価になります。これは、要素の並べ替えと分析のコストが、キャンバスに描画できるUI要素の数と線形関係にあるためです。
- キャンバスが頻繁に汚れている場合は、比較的少ない変更でキャンバスを更新するのに時間がかかりすぎる可能性があります。
キャンバス上の要素の数が増えると、これらの問題は両方とも深刻になります。
重要な注意:特定のキャンバス上の描画可能なUI要素が変更された場合、キャンバスはバッチビルドプロセスを再実行する必要があります。このプロセスは、変更されたかどうかに関係なく、キャンバス上のすべての描画可能なUI要素を再分析します。「変更」とは、スプライトレンダラーに割り当てられたスプライト、遷移の位置とスケール、テキストグリッドに含まれるテキストなど、UIオブジェクトの外観に影響を与える変更を指すことに注意してください。
子供の注文
UnityUIは後ろから前に構築され、階層内のオブジェクトの順序によって並べ替え順序が決まります。階層内の前のオブジェクトは、オブジェクトの背後にあるオブジェクトと見なされます。バッチは、上から下の階層に移動し、同じマテリアル、同じテクスチャを使用し、中間レイヤーを使用しないすべてのオブジェクトを収集することによって構築されます。「中間層」は、異なるマテリアルのグラフィックオブジェクトであり、その境界ボックスは、他の2つのヒット可能なオブジェクトと重なり、2つのヒット可能なオブジェクト間の階層に配置されます。中間層はバッチを強制的に中断します。
UnityUI分析ツールのセクションで説明したように、UIアナライザーとフレームワークデバッガーを使用して、UIの中間層を検査できます。この場合、1つのドローアブルオブジェクトが他の2つのドローアブルオブジェクトと相互作用します。そうしないと、これらのオブジェクトは壊れやすくなります。
この問題の最も一般的なケースは、テキストとスプライトが互いに近接していることです。テキストグリフのポリゴンはほぼ透明であるため、テキストのバウンディングボックスが近くのSPTERで見えないように重なる可能性があります。この問題は、次の2つの方法で解決できます。
- クリック可能なオブジェクトがクリック不可能なオブジェクトによって挿入されないように、ドローアブルオブジェクトを並べ替えます。つまり、クリック不可能なオブジェクトをクリック可能なオブジェクトの上または下に移動します。
- オブジェクトの位置を調整して、目に見えない重なり合うスペースを排除します。
どちらの操作もUnityEditorで実行でき、UnityFrameDebuggerを開いて有効にすることもできます。UnityFrameworkDebuggerに表示される描画呼び出しの数を観察するだけで、UI要素の重複による無駄な描画呼び出しの数を最小限に抑える順序と場所を見つけることができます。
キャンバスの分割
最も単純な場合を除いて、すべての場合において、通常、要素をサブキャンバスに移動するか、要素を同じキャンバスに移動することによって、キャンバスを分離することをお勧めします。
ブラザーキャンバスは、UIの特定の部分をUIの他の部分から分離して、描画の深さを制御し、常に他のレイヤーの上または下に配置する必要がある場合に最適です(チュートリアルの矢印など)。
他のほとんどの場合、子キャンバスは親キャンバスから表示設定を継承するため、より便利です。
一見したところ、UIを複数のサブキャンバスに分割することがベストプラクティスですが、キャンバスシステムは異なるキャンバス上のバッチもマージしないことに注意してください。パフォーマンスの高いUI設計では、再構築コストを最小限に抑えることと、無駄な描画呼び出しを最小限に抑えることのバランスを取る必要があります。
一般的なガイドライン
キャンバスは、その構成要素であるドローアブルコンポーネントのいずれかが変更されると再バッチ処理されるため、通常、重要なキャンバスを少なくとも2つの部分に分割するのが最善です。さらに、要素が同時に変更されることが予想される場合は、同じキャンバス上に要素を同じ場所に配置することをお勧めします。たとえば、プログレスバーとカウントダウンタイマー。これらはすべて同じ基になるデータに依存しているため、同時に更新する必要があるため、同じキャンバスに配置する必要があります。
キャンバス上に、背景やラベルなど、静的で不変の要素をすべて配置します。これらは、キャンバスが初めて表示されたときに1回バッチ処理され、その後再バッチ処理する必要はありません。
2番目のキャンバスに、すべての「動的」要素(頻繁に変更される要素)を配置します。これにより、このキャンバスがほとんど汚れていることが保証されます。動的要素の数が非常に多くなる場合は、動的要素を、絶えず変化する要素のセット(プログレスバー、タイマーの読み取り、アニメーションなど)と、たまにしか変化しない要素のセットにさらに細分化する必要があります。
実際、これは実際には非常に困難です。特に、UIコントロールをプレハブにパッケージ化する場合はなおさらです。多くのUIは、キャンバスを細分化することを選択し、より高価なコントロールをサブキャンバスに分割します。
Unity5.2と最適化されたバッチ処理
Unity 5.2では、バッチコードが大幅に書き直され、パフォーマンスはUnity 4.6、5.0、5.1よりもはるかに優れています。さらに、複数のコアを備えたデバイスでは、UnityUIシステムはほとんどの処理をワーカースレッドに移動します。一般的に、Unity 5.2は、UIを数十のサブキャンバスに分割する必要性を減らします。モバイルデバイスの多くのUIは、2つまたは3つのキャンバスで実行できるようになりました。
Unity 5.2の最適化の詳細については、このブログ投稿を参照してください。
UnityUIでの入力とレイキャスティング
デフォルトでは、UnityUIはGraphic Raycasterコンポーネントを使用して、タッチイベントやポインタホバリングイベントなどの入力イベントを処理します。これは通常、独立した入力マネージャーコンポーネントによって処理されます。名前にもかかわらず、独立した入力マネージャーは「ユニバーサル」入力マネージャーシステムを意味し、ポインターとタッチを処理します。
モバイルでの誤ったマウス入力検出(5.3)
Unity 5.4より前では、アクティブな各CanvasとGraphic Raycasterは、現在使用可能なタッチ入力がない限り、フレームごとにレイキャストを実行してポインターの位置を確認していました。プラットフォームに関係なく、これはiOSおよびAndroidデバイスで発生します。マウスのないiOSおよびAndroidデバイスは、マウスの位置を照会し、その位置の下にあるUI要素を見つけて、ホバーイベントを送信する必要があるかどうかを判断しようとします。
これはCPU時間の無駄であり、Unityアプリケーションを使用するとCPUフレーム時間の5%以上が見られます。
この問題はUnity5.4で解決されています。5.4以降、マウスのないデバイスはマウスの位置を照会せず、不要なレイキャスティングを実行しません。
5.4より前のバージョンを使用している場合は、モバイル開発者が独自の入力マネージャークラスを作成することを強くお勧めします。これは、UnityUIソースからUnitedInputManagerの標準入力マネージャーをコピーし、ProcessMouseEventメソッドとこのメソッドへのすべての呼び出しをコメントアウトするのと同じくらい簡単です。
レイキャストの最適化
グラフィックレイキャスターは、「レイキャストターゲット」がtrueに設定されているすべてのグラフィックコンポーネントを反復処理する比較的単純な実装です。Raycastターゲットごとに、Raycasterは一連のテストを実行します。Raycastターゲットがすべてのテストに合格すると、ヒットリストに追加されます。
Raycastの実装の詳細
テストは次のとおりです。
- Raycastターゲットがアクティブな場合は、有効にして描画します(つまり、ジオメトリがあります)
- 入力ポイントがRaycastターゲットに接続されたRectTransformにある場合
- RaycastターゲットがICanvasRaycastフィルターコンポーネント(任意の深さ)を持っているか、その子であり、RaycastフィルターコンポーネントがRaycastを許可する場合。
次に、ヒットしたRaycastターゲットのリストを深度で並べ替え、リバースターゲットをフィルタリングし、それらをフィルタリングして、カメラの背後に表示されている要素(つまり、画面に表示されていない要素)が削除されるようにします。
グラフィックレイキャスターの「ブロッキングオブジェクト」プロパティに対応するフラグが設定されている場合、グラフィックレイキャスターは光線を3Dまたは2D物理システムに投影することもできます。(スクリプトでは、このプロパティの名前はBlock ingObjectsです。)
2Dまたは3Dブロッキングオブジェクトが有効になっている場合、レイキャストブロッキング物理層の2Dまたは3Dオブジェクトの下に描画されたレイキャストターゲットもヒットリストから削除されます。
次に、ラストクリックリストに戻ります。
レイキャスティング最適化のヒント
すべてのRaycastターゲットをグラフィカルなRaycasterでテストする必要があることを考えると、ベストプラクティスは、ポインターイベントを受信する必要があるUIコンポーネントでのみ「RaycastTarget」設定を有効にすることです。Raycastターゲットリストが小さいほど、トラバースする必要のあるレベルが浅くなり、各Raycastテストを高速化できます。
背景とテキストの色を変更したいボタンなど、ポインタイベントに応答する必要のある複数の描画可能なUIオブジェクトを持つ複合UIコントロールの場合、通常、複合UIコントロールのルートにRaycastターゲットを配置するのが最適です。単一のレイキャストターゲットがポインタイベントを受信すると、複合コントロール内の対象の各コンポーネントにイベントを転送できます。
階層の深さとレイキャストフィルター
レイキャストフィルターを検索する場合、各グラフィックレイキャストは変換階層をトラバースしてルートに到達します。この操作のコストは、階層の深さに比例して増加します。階層内の各変換に接続されているすべてのコンポーネントをテストして、ICanvasRayCastFilterが実装されているかどうかを判断する必要があるため、これは安価な操作ではありません。
CanvasGroup、Image、MASK、RectMask2Dなど、ICanvasRayCastFilterを使用できる標準のUnitedUIコンポーネントがいくつかあるため、このトラバーサルは無視できません。
サブキャンバスとOverrideSortingプロパティ
サブキャンバスのoverrideSortingプロパティにより、グラフィカルなRaycastテストは変換階層の上昇を停止します。シーケンシングやレイブロードキャスト検出の問題を引き起こさずに有効にできる場合は、レイブロードキャスト階層をトラバースするコストを削減するために使用する必要があります。