原文鏈接:https://unity3d.com/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input
? ? ? ? ? ? 填充率峭状、Canvas和輸入
? ? ? ? 這一章節(jié)討論了UGUI 構(gòu)建過程中廣泛存在的問題浑度。
修復(fù)填充率問題
? ? ? ? 對(duì)于減輕GPU片段流水線上的壓力有兩種行動(dòng)方案:
? ? ? ? 1.減少片段著色器的復(fù)雜性罗晕。
? ? ? ? ? ? ? ? ——有關(guān)更多詳細(xì)信息,請(qǐng)參閱“UI著色器和低規(guī)格設(shè)備”部分
? ? ? ? 2.減少必須采樣的像素?cái)?shù)量。
? ? ? ? 由于UI著色器通常是標(biāo)準(zhǔn)化的,最常見的問題就是填充率的過度使用。導(dǎo)致這個(gè)問題最普遍的原因是UI元素大量重疊底靠,或是有多個(gè)UI元素占據(jù)大部分的屏幕。這些問題都會(huì)導(dǎo)致高等級(jí)的過度繪制特铝。
? ? ? ? 為了減輕填充率的過度使用并且減少過度繪制暑中,請(qǐng)考慮以下補(bǔ)救措施:
消除看不見的UI
? ? ? ? 簡單的禁用玩家看不見的元素是對(duì)現(xiàn)有UI元素重新設(shè)計(jì)要求最小的方法壹瘟,對(duì)于這種方法最常見的情況是打開了一個(gè)具有不透明背景的全屏UI。此時(shí)鳄逾,在全屏UI下的任何UI元素都可以被禁用稻轨。
? ? ? ? 最簡單的方法是禁用根GameObject或是包含UI元素的GameObject。有關(guān)替代解決方案雕凹,請(qǐng)參閱Disabling Canvas Renderers部分殴俱。
禁用不可見的攝像機(jī)輸出
? ? ? ? 如果在UGUI中打開了一個(gè)擁有不透明背景的全屏UI,world-space攝像機(jī)仍然會(huì)對(duì)在UI后面的獨(dú)立的3D 場景進(jìn)行渲染枚抵。渲染器并不知道全屏UGUI會(huì)遮擋整個(gè)3D場景线欲。
? ? ? ? 因此如果打開了一個(gè)不透明的全屏UI,禁用任何或者是全部的world-space攝像機(jī)將減少渲染3D世界的無用工作汽摹,從而減少GPU的壓力李丰。
? ? ? ? 注意:如果Canvas被設(shè)置為Screen Space – Overlay,不管場景中可用的攝像機(jī)有多少逼泣,Canvas都將被繪制趴泌。
大部分被遮擋的攝像機(jī)
? ? ? ? 許多“全屏”UI并不實(shí)際上遮擋整個(gè)3D世界,而是留下了一個(gè)小的部分可以看到3D世界拉庶。在這種情況下踱讨,使用一個(gè)渲染的紋理來拍攝這部分3D世界可能更為理想。如果這部分可見的3D世界被緩存在渲染紋理中砍的,那么實(shí)際的world-space攝像機(jī)就可以被禁用,此時(shí)被緩存的渲染紋理就作為3D世界的冒充版本顯示在UI屏幕后面莺治。
基于構(gòu)圖的UI
? ? ? ? 在設(shè)計(jì)者中廓鞠,基于構(gòu)圖來合并與層疊獨(dú)立的背景與UI元素來構(gòu)成最終的UI是非常普遍的。雖然這樣做相對(duì)簡單谣旁,而且易于迭代床佳,但是由于UGUI使用的是透明渲染隊(duì)列,所以無法高效工作榄审。
? ? ? ? 考慮一個(gè)簡單的UI砌们,有一個(gè)背景、一個(gè)按鈕和一些文字在按鈕上搁进。在像素顯示文字的情況下浪感,GPU必須先采樣背景紋理,然后是按鈕的紋理饼问,最后是字體的紋理影兽,這三層全部都要采樣。當(dāng)UI的復(fù)雜性增加時(shí)莱革,更多裝飾性的元素將被層疊在背景之上峻堰,需要采樣的數(shù)量將迅速增加讹开。
? ? ? ? 如果發(fā)現(xiàn)一個(gè)大的UI被填充率所束縛,最好的解決方案就是創(chuàng)建一個(gè)單獨(dú)的UI Sprite捐名,它融合了許多裝飾性的或者是不變的UI元素在它的背景紋理之上旦万。這樣做減少了為了達(dá)到設(shè)計(jì)目的而必須重疊防止的元素?cái)?shù)量,但是這樣做也耗費(fèi)勞動(dòng)力并且也增加了項(xiàng)目圖集的大小镶蹋。
? ? ? ? 這種將創(chuàng)建給定UI的需要重疊的元素合并到特定的UI Sprite上的做法也適用于子元素成艘。考慮一個(gè)商店UI帶有產(chǎn)品滾動(dòng)的窗格梅忌,每個(gè)產(chǎn)品UI元素有一個(gè)邊框狰腌、一個(gè)背景和一些圖標(biāo)來表示價(jià)格、名字和其他信息牧氮。
? ? ? ? 這個(gè)商店UI需要一個(gè)背景琼腔,但是由于產(chǎn)品要在背景上滑動(dòng),產(chǎn)品UI元素?zé)o法融合到商店UI的背景紋理之上踱葛。然而丹莲,邊框、價(jià)格尸诽、名字和產(chǎn)品UI元素的其他元素可以融合到產(chǎn)品的背景上甥材。根據(jù)圖標(biāo)的大小和數(shù)量,填充率的節(jié)省相當(dāng)可觀性含。
? ? ? ? 合并分層元素有一些缺點(diǎn)洲赵。特殊的元素不能再重復(fù)利用,這就需要額外的藝術(shù)家人力資源來創(chuàng)建商蕴。增加大的新紋理可能會(huì)顯著增加需要來存儲(chǔ)UI紋理的內(nèi)存數(shù)量叠萍,特別是UI紋理未能按需求加載和卸載的情況下。
UI著色器和低規(guī)格設(shè)備
? ? ? ? UGUI使用的內(nèi)置著色器包含了對(duì)隱藏绪商、裁剪和許多其他復(fù)雜操作的支持苛谷。由于這種復(fù)雜性的增加,在iPhone4這種較低端設(shè)備上格郁,UI著色器的表現(xiàn)較簡單的Unity2D著色器相比表現(xiàn)較差腹殿。
? ? ? ? 如果一個(gè)針對(duì)低端設(shè)備的應(yīng)用程序不需要隱藏、裁剪和其他奇特的功能,那么就可以創(chuàng)建一個(gè)自定義的著色器來省略沒有使用的操作,比如下面這個(gè)最簡單的UI著色器:(著色器代碼見原網(wǎng)頁)
UI Canvas rebuild
? ? ? ? 要顯示任何UI咖耘,UI系統(tǒng)必須要為顯示在屏幕上的每個(gè)UI組件構(gòu)建幾何體物蝙。這包括了運(yùn)行動(dòng)態(tài)布局代碼,生成多邊形來變現(xiàn)UI文本中字符串的字符,還有融合盡可能多的幾何體到單個(gè)網(wǎng)格中來最小化draw call。這個(gè)過程有很多步驟治泥,在本指南開始的基礎(chǔ)基礎(chǔ)概念部分有詳細(xì)介紹暂幼。
? ? ? ? Canvas rebuild成為性能問題有兩個(gè)主要原因:
? ? ? ? 1.如果一個(gè)Canvas上有大量要繪制的UI元素筏勒,那么計(jì)算batch本身就變的非常昂貴。這是因?yàn)樵谂帕泻头治鲞@些元素上的花費(fèi)比在Canvas上繪制這些UI元素的增長更多旺嬉。
? ? ? ? 2.如果Canvas的dirty特別頻繁管行,那么就有可能花費(fèi)更多的時(shí)間在刷新一個(gè)Canvas相對(duì)較小的改變上。
? ? ? ? 隨著一個(gè)Canvas上元素?cái)?shù)量的增加邪媳,上面兩個(gè)問題會(huì)越來越嚴(yán)重捐顷。
? ? ? ? 重要提示:在給定的Canvas上任何要繪制的UI元素改變,這個(gè)Canvas必須重新進(jìn)行batch的build過程雨效。該過程重新分析Canvas上的每個(gè)可繪制UI元素迅涮,而不管它是否已經(jīng)改變。請(qǐng)注意徽龟,“改變”是指影響UI對(duì)象外觀的任何改變叮姑,包括Sprite Renderer中指定的Sprite、transform的position和scale變化据悔、包含在文本網(wǎng)格中的文本等等传透。
子物體排序
? ? ? ? UGUI的建立是從后至前的,子對(duì)象在層級(jí)中的排序決定了它們的建立順序极颓。在層級(jí)循序中靠前的物體將被建立在層級(jí)順序中靠后物體的后面朱盐。batch的build是從層級(jí)順序的上走到下,并收集具有相同材質(zhì)的游戲物體菠隆,即有相同紋理且沒有中間層的對(duì)象(“中間層”是具有不同材質(zhì)的圖形對(duì)象兵琳,其邊界框與另外可合batch的對(duì)象重疊,并放置在兩個(gè)可batch對(duì)象之間的層次結(jié)構(gòu)中)骇径。中間層的存在導(dǎo)致batch被打斷闰围。
? ? ? ? 正如Unity Frame Debugger部分所述, Frame Debugger可以用來檢查中間層的UI既峡。就是上述這種情況,一個(gè)要繪制的對(duì)象插入到另外兩個(gè)要繪制的原本可batch的對(duì)象之間碧查。
? ? ? ? 這個(gè)問題最為常發(fā)生于當(dāng)text和sprite位于彼此靠近時(shí):text的邊界框可能不可見地重疊附近的sprite运敢,因?yàn)閠ext字形的多邊形大多數(shù)都是透明的。這個(gè)問題可以通過兩種方式解決:
? ? ? ? 1.對(duì)要繪制的對(duì)象進(jìn)行重新排序忠售,以確保兩個(gè)可以合batch的對(duì)象不會(huì)被不能合batch的對(duì)象打破传惠。也就是說,移動(dòng)不可合batch的對(duì)象到可合batch的對(duì)象的上方或者下方稻扬。
? ? ? ? 2.調(diào)整各個(gè)對(duì)象的位置來消除不可見空間的重疊卦方。
? ? ? ? 上述兩個(gè)操作都可以在Unity Frame Debugger打開并可用的情況下在Untiy Editor中執(zhí)行。通過簡單地觀察Unity Frame Debugger中可見的drawcall次數(shù)泰佳,就可以找到一個(gè)最合適的順序和位置來使由于UI元素重疊而導(dǎo)致的drawcall浪費(fèi)減少到最小盼砍。
拆分Canvas
? ? ? ? 除了一些特殊的情況尘吗,將Canvas拆分通常是一個(gè)好主意〗阶可以將元素移動(dòng)到子Canvas或者是同級(jí)Canvas中睬捶。
? ? ? ? 同級(jí)Canvas最常適用于UI中的某一部分必須與其他部分區(qū)分繪制深度,經(jīng)常在其他層的上面或者下面近刘。(例如教程中的箭頭)
? ? ? ? 在其他大多數(shù)情況下擒贸,子Canvas可以更方便的從父Canvas繼承顯示設(shè)置。
? ? ? ? 乍看之下觉渴,將整個(gè)UI拆分為多個(gè)子Canvas是一種最佳做法介劫,但要知道,Canvas系統(tǒng)也不會(huì)在分離的Canvas之間合成batch案淋。高性能的UI設(shè)計(jì)要求在最小化rebuild和最小化drawcall浪費(fèi)中取得一個(gè)平衡座韵。
一般準(zhǔn)則
? ? ? ? 由于Canvas的rebatch過程在任何時(shí)候都會(huì)包含所有要繪制的子組件的改變,所以最好將那些不是特殊情況的Canvas拆分成至少兩部分哎迄。另外回右,如果一些元素可能會(huì)同時(shí)改變,最好將他們放到同一個(gè)Canvas中漱挚。比如這里有一個(gè)進(jìn)度條和一個(gè)倒數(shù)計(jì)時(shí)器翔烁,它們倆依賴同樣的底層數(shù)據(jù),并且將同時(shí)被更新旨涝,所以它們應(yīng)該被放在同一個(gè)Canvas上蹬屹。
? ? ? ? 在一個(gè)Canvas上,放置所有靜態(tài)的不改變的元素白华,比如背景和標(biāo)簽慨默。當(dāng)Canvas一開始顯示時(shí)它們將會(huì)被batch一次,然后它們就不會(huì)再需要被rebatch了弧腥。
? ? ? ? 在第二個(gè)Canvas上厦取,放置所有的動(dòng)態(tài)的、頻繁變化的元素管搪。這個(gè)Canvas主要是用來rebatch被標(biāo)為dirty的元素的虾攻。如果動(dòng)態(tài)元素的數(shù)量變得非常多,那就要對(duì)所有動(dòng)態(tài)元素進(jìn)行更細(xì)的拆分更鲁,一些是經(jīng)常會(huì)改變的(例如進(jìn)度條霎箍、計(jì)時(shí)器顯示、所有動(dòng)畫)澡为,還有一些是偶爾改變的漂坏。
? ? ? ? 事實(shí)上這些在實(shí)際使用中是非常困難的,尤其是將UI控件封裝成prefab的時(shí)候。許多UI轉(zhuǎn)而選擇拆分Canvas顶别,將更消耗性能的控件拆分到子Canvas上谷徙。
Unity5.2和優(yōu)化Batch
? ? ? ? 在Unity5.2中,進(jìn)行batch的代碼被充分重構(gòu)筋夏,與Unity4.6蒂胞、5.0、5.1版本相比性能更加高效条篷。而且骗随,在多核設(shè)備(一個(gè)核心以上)上,UGUI系統(tǒng)將處理工作移動(dòng)到工作線程中赴叹。一般來說鸿染,Unity5.2減少了將UI拆分成幾十個(gè)子Canvas的偏激需求。移動(dòng)設(shè)備上的許多UI現(xiàn)在可以只用2乞巧、3個(gè)Canvas就可以提高性能涨椒。
? ? ? ? 有關(guān)Unity 5.2優(yōu)化的更多信息可以在這篇博客文章中找到。(鏈接見原網(wǎng)頁)
UGUI中的輸入和射線
? ? ? ? 默認(rèn)情況下绽媒,UGUI使用Graphic Raycaster組件來處理輸入事件蚕冬,例如觸摸事件和點(diǎn)擊保持事件。這通常由Standalone Input Manager組件來處理是辕。盡管Standalone Input Manager叫這個(gè)名字囤热,但它是一個(gè)通用的輸入管理系統(tǒng),點(diǎn)擊和觸摸它都會(huì)處理获三。
移動(dòng)設(shè)備上的錯(cuò)誤的鼠標(biāo)檢測(5.3)
? ? ? ? 在Unity 5.4之前旁蔼,只要當(dāng)前沒有正確的觸摸輸入,每個(gè)擁有Graphic Raycaster的激活的Canvas每幀都將進(jìn)行一次射線檢測來確定點(diǎn)擊點(diǎn)的位置疙教。不論任何平臺(tái)棺聊,這都將進(jìn)行。無鼠標(biāo)的iOS和Android設(shè)備仍然會(huì)確定鼠標(biāo)位置贞谓,并嘗試發(fā)現(xiàn)該位置下有哪些UI元素限佩。(這種情況的發(fā)生是為了確定是否有任何鼠標(biāo)懸停事件需要觸發(fā))
? ? ? ? 這浪費(fèi)了CPU的時(shí)間,至少浪費(fèi)了Unity應(yīng)用程序的5%或者更多的CPU幀執(zhí)行時(shí)間裸弦。
? ? ? ? Unity 5.4版本解決了這個(gè)問題祟同。從5.4以后,沒有鼠標(biāo)的設(shè)備將不會(huì)查詢鼠標(biāo)位置烁兰,也不會(huì)發(fā)射不必??要的射線。
? ? ? ? 如果使用的是早于5.4的Unity版本徊都,強(qiáng)烈建議移動(dòng)開發(fā)人員創(chuàng)建自己的輸入管理類沪斟。這非常簡單,從UGUI開源代碼中拷貝Unity的Standard Input Manager代碼,注釋掉ProcessMouseEvent方法和所有對(duì)這個(gè)方法的調(diào)用主之。
Raycast優(yōu)化
? ? ? ? Graphic Raycaster是一個(gè)相對(duì)直接的實(shí)現(xiàn)择吊,它迭代所有Raycast Target設(shè)為true的Graphic組件。對(duì)于每一個(gè)Raycast Target設(shè)為true的Graphic組件槽奕,Graphic Raycaster會(huì)執(zhí)行一系列測試几睛。如果該組件通過了所有測試,則會(huì)被添加到命中的列表中粤攒。
? ? ? ? Raycast實(shí)現(xiàn)細(xì)節(jié)
? ? ? ? 上述的測試是:
? ? ? ? 1.如果檢測到的目標(biāo)的GameObject是激活的所森、UI組件是可用的,那就繪制(即具有幾何體)夯接。
? ? ? ? 2.如果輸入點(diǎn)在被檢測到的UI元素的RectTransform范圍內(nèi)焕济。
? ? ? ? 3.如果被檢測到的目標(biāo)擁有,或者其任意深度的子物體擁有任何實(shí)現(xiàn)ICanvasRaycastFilter的組件盔几,并且這個(gè)組件允許進(jìn)行射線檢測晴弃。
? ? ? ? 接著檢測目標(biāo)列表會(huì)對(duì)元素按照深度排序,調(diào)整順序不對(duì)的目標(biāo)逊拍,并確認(rèn)要在攝像機(jī)后面渲染的元素(即在屏幕中不可見)被移除了上鞠。
? ? ? ? 如果3D或者2D的物理系統(tǒng)各自的Graphic Raycaster的“Blocking Objects”屬性被標(biāo)記,那么Graphic Raycaster也會(huì)向它們投射射線(在腳本中芯丧,該屬性被命名為blockingObjects)芍阎。
? ? ? ? 如果3D或者2D的的“Blocking Objects”被啟動(dòng),那么任何繪制在一個(gè)射線遮擋物理層上的2D或是3D物體下的被檢測到的目標(biāo)將會(huì)被從列表中移除注整。
? ? ? ? 返回最終的列表能曾。
射線優(yōu)化技巧
? ? ? ? 鑒于所有射線檢測目標(biāo)都必須由Graphic Raycaster進(jìn)行測試,因此最好的做法是僅在必須接收點(diǎn)擊事件的UI組件上啟用“Raycast Target”設(shè)置肿轨。檢測目標(biāo)列表越小寿冕,必須遍歷的層級(jí)越淺,每次射線檢測的速度越快椒袍。
? ? ? ? 對(duì)于那些有多個(gè)必須對(duì)點(diǎn)擊事件響應(yīng)的UI物體的復(fù)合UI控件驼唱,比如一個(gè)按鈕它希望同時(shí)改變它的Text和背景顏色,這種情況一般最好在復(fù)合UI控件的根物體上設(shè)置一個(gè)單獨(dú)的檢測目標(biāo)驹暑。當(dāng)單個(gè)的檢測目標(biāo)接收到了點(diǎn)擊事件玫恳,那么它可以將這個(gè)事件發(fā)送給這個(gè)復(fù)合控件中要響應(yīng)的組件。
層級(jí)深度與raycast filter
? ? ? ? 當(dāng)尋找raycast filter的時(shí)候优俘,每個(gè)Graphic Raycast都會(huì)對(duì)根物體層級(jí)進(jìn)行從頭至尾的遍歷京办。這個(gè)操作的性能消耗與層級(jí)的深度呈線性增長關(guān)系。層級(jí)中所有擁有Transform的組件必須經(jīng)過檢查帆焕,看它們是否實(shí)現(xiàn)了ICanvasRaycastFilter惭婿,所以這個(gè)操作的性能耗費(fèi)并不廉價(jià)。
? ? ? ? 有一些獨(dú)立的UGUI組件實(shí)現(xiàn)了ICanvasRaycastFilter,比如CanvasGroup, Image, Mask和RectMask2D财饥,所以這個(gè)遍歷不會(huì)簡單的結(jié)束换吧。
子Canvas和OverrideSorting屬性
? ? ? ? 子Canvas中的OverrideSorting屬性將會(huì)造成Graphic Raycast測試停止遍歷Transform層級(jí)。如果啟動(dòng)它不會(huì)帶來排序或者射線檢測的問題钥星,那么就應(yīng)該使用它來降低射線進(jìn)行層級(jí)遍歷的性能成本沾瓦。