Unity基礎教程系列(新)(四)——測量性能(MS and FPS)

2021-02-20 壹種念頭

目錄

  1 分析Unity

        1.1 遊戲窗口 Statistics

        1.2 動態合批

        1.3 GPU Instancing

        1.4 Frame Debugger

        1.5 額外的燈光

        1.6 Profiler

        1.7 分析一次構建

  2 展示幀率

        2.1 UI面板

        2.2 Text

        2.3 更新顯示

        2.4 平均幀率

        2.5 最好和最差

        2.6 幀持續時間

        2.7 內存分配

  3 自動進行函數切換

        3.1 函數循環

        3.2 隨機函數

        3.3 函數插值

        3.4 過渡


本文重點內容:
1、使用 game window stats, frame debugger, 和 profiler
2、比較動態批處理, GPU instancing, and SRP batcher
3、顯示幀率
4、循環自動的執行函數
5、不同函數之間平滑過渡

這是關於學習使用Unity的基礎知識的系列教程中的第四篇。對測量性能的介紹。我們還將在函數庫中添加從一個函數轉換為另一個函數的功能。

本教程是CatLikeCoding系列的一部分,原文地址見文章底部。

本教程使用Unity 2019.4.12f1製作。

(介于波浪和球體之間)


1 分析Unity


Unity持續渲染新幀。為了使任何運動看起來都流暢,它必須足夠快地執行此操作,以便我們將圖像序列看起來是連續運動的。通常,每秒至少需要30幀(簡稱FPS),而60 FPS是理想的目標。這些數字經常出現是因為許多設備的顯示刷新率為60赫茲。如果不關閉垂直同步功能,則繪製幀的速度不能超過此(垂直同步)速度,這會導致圖像撕裂。如果無法達到一致的60 FPS,則下一個最佳速率是30 FPS,即每兩個顯示刷新一次。降低15幀/秒將不足以進行流暢的圖像運動。


其他常見的監視器刷新率是多少?
75Hz,85Hz和144Hz臺式顯示器也很常見。對於競賽類型的遊戲場景,刷新率更高。因此,如果你的應用程式可以可靠地達到80FPS,則在所有顯示器上啟用VSync後,它的性能都將很好。如果只能達到60FPS,則75Hz的顯示器將以37.5FPS的速度下降一半,85Hz的顯示器將減至42.5FPS的一半,而144Hz的顯示器將以48FPS的速度下降至三分之一。但是,這是在假定性能穩定的前提下。實際上,幀速率可能在刷新速率的倍數之間波動。

是否可以達到目標幀速率取決於處理單個幀需要多長時間。為了達到60FPS,我們必須在不到16.67毫秒的時間內更新和渲染每個幀。30FPS的時間預算為每幀33.33 ms。


當圖形運行時,我們可以通過簡單地觀察它來了解其運動的平滑程度,但這是一種非常不精確的測量其性能的方法。如果運動看起來很平穩,則可能超過30FPS,如果看起來卡頓,則可能會小於30FPS。由於性能不一致,當前可能會很平穩,而下一刻可能就會卡頓。這可能是由於我們應用程式的差異引起的,也可能是由於同一設備上運行的其他應用程式引起的。如果我們勉強達到60FPS,那麼我們最終可能會在30FPS和60FPS之間來回快速變動,儘管平均FPS很高,但仍會感到不流暢。因此,要更好地了解正在發生的請客,我們需要更精確地衡量性能。Unity有一些工具可以幫助我們解決這個問題。


1.1 遊戲窗口 Statistics


遊戲窗口有一個Statistics覆蓋面板,可以通過其Stats工具欄按鈕激活該面板。它顯示對最後渲染的幀進行的測量。雖然它並不能告訴我們太多信息,但是它是我們可以用來了解正在發生的情況的最簡單的工具。在編輯模式下,遊戲窗口通常僅在某些更改後才偶爾更新。在播放模式下,它會一直刷新。


以下統計信息是針對使用默認渲染管道的torus函數和解析度為100的圖形繪製的,從現在開始,我將其稱為DRP。我為遊戲窗口打開了VSync,因此刷新與我的60 Hz顯示屏同步。

(DRP的統計信息)


統計數據顯示,CPU主線程花費了23.6ms,渲染線程花費了27.8ms。你可能會得到不同的結果,這取決於你的硬體。在我的例子中,它預示渲染整個幀需要51.4ms,但是統計面板報告的是36FPS,匹配渲染線程時間。FPS指標似乎取了兩者中最壞的,並假設與幀速率匹配。這是一個過分簡化,只考慮CPU方面,忽略了GPU和顯示。實際幀率可能更低。

什麼是線程?
在Unity應用程式的情況下,線程是子進程。可以有多個線程同時並行運行。統計信息顯示在上一幀期間Unity的主線程和渲染線程運行了多長時間。

除了持續時間和FPS指示之外,統計面板還顯示有關渲染內容的各種詳細信息。有30003批次,顯然通過合批的次數為零。這些是發送到GPU的繪製命令。我們的圖形包含10000點,因此似乎每個點都被渲染了3次。一次用於深度Pass,一次用於陰影投射器(也單獨列出了),一次用於渲染每個點的最終立方體。其他三個批次用於其他工作,例如與我們的圖形無關的天空盒和陰影處理。還有六個set-pass調用,這可以通過將GPU重新配置為以不同的方式呈現(例如使用不同的材質)來實現。

(URP的統計信息)


如果我們切換到URP,統計數據是不同的。它渲染速度更快,在這種情況下,主CPU線程比渲染線程慢。原因很容易猜到:只有20001批,比DRP少了10000個批次。這是因為URP沒有為定向陰影使用單獨的深度通道。統計數據顯示零陰影投射器,但那是因為這一項只能顯示DRP的數據。


另一個奇怪的事情是,Saved by batching可能顯示負數。發生這種情況的原因是,默認情況下,URP使用SRP批處理程序,而統計信息面板不理解它。SRP批處理程序不會消除單個繪製命令,但可以使它們效率更高。為了說明這一點,請選擇我們的URP資產,並在其檢查器底部的Advanced部分下禁用SRP Batcher。

(URP高級設置)


禁用了SRP batcher後,URP的性能會差得多。

(沒有SRP batcher的URP統計窗口)


1.2 動態合批


除了SRP Batcher,URP還具有另一個用於動態批處理的開關。這是一項古老的技術,可以將小網格物體動態地組合成一個較大的網格物體,然後將其渲染。為URP啟用它會將批次減少到10023,並且統計面板顯示已節省了9978次Draw Call。

(開啟了動態合批的URP統計數據)


在我的例子中,SRP批處理程序和動態批處理具有相當好的性能,因為立方體網格是動態批處理的理想(網格小)對象。


SRP批處理不適用於DRP,但是我們可以為其啟用動態批處理。可以在Player項目設置的Other Settings 部分找到切換開關,該設置位於將顏色空間設置為線性的位置下方。僅在不使用可編寫腳本的渲染管道設置時可見。

(帶有動態批處理的DRP統計信息)


動態批處理對於DRP更有效,節省了29964批,將它們減少到僅39個批次。這是一個重大優化,但仍然不如URP快。


1.3 GPU Instancing


提高渲染性能的另一種方法是啟用GPU實例化。這樣就可以使用單個繪製命令來告訴GPU使用相同的材質繪製一個網格的許多實例,從而提供一系列轉換矩陣以及其他可選的實例數據。在這種情況下,我們必須針對每種材質啟用它。我們有一個Enable GPU Instancing開關。

( GPU Instancing開啟的材質)


與GPU實例化相比,URP更喜歡SRP批處理程序,因此為了使其適用於我們的點陣,需要禁用SRP batcher。然後我們可以看到批處理數量減少到只有45,比動態批處理好得多。我們稍後會解釋造成這種差異的原因。

(開啟了GPU instancing 的URP統計)


從這些數據我們可以得出結論,對於URP GPU Instancing最好,然後是動態批處理,然後是SRP批處理器。但是差異很小,指示的FPS在所有情況下都比我的顯示刷新率高,因此對於我們的視圖來說,它們似乎等效。唯一明確的結論是,不使用這些都不是一個好主意。


對於DRP,GPU實例化似乎比動態批處理要好一些,這兩種方法都比不使用動態批處理要好得多。

(開啟了GPU instancing的DRP統計面板)


1.4 Frame Debugger


統計信息面板可以告訴我們使用動態批處理與使用GPU實例化有所不同,但沒有告訴我們原因。為了更好地了解發生了什麼,我們可以使用通過Window/ Analysis / Frame Debugger打開的幀調試器。通過其工具欄按鈕啟用後,它將顯示發送到GPU的遊戲窗口最後一幀的所有繪製命令的列表,這些列表按概要採樣分析分組。該列表顯示在其左側。在其右側顯示了特定選定繪製命令的詳細信息。此外,遊戲窗口將顯示漸進的繪製狀態,繪製手動選擇的命令。

為什麼我的電腦突然變熱了?
Unity使用的技巧就是需要反覆渲染相同的幀來顯示繪製幀的中間狀態。只要幀調試器處於活動狀態,它就會執行此操作。所以確保在不需要幀調試器時禁用它。

在這裡,我們必須處於播放模式,因為那是我們的圖形被繪製的時候。啟用幀調試器將暫停播放模式,這允許我們檢查繪製命令的層次結構。讓我們先為DRP做這個,先不使用動態批處理或GPU實例化。

(DRP的幀調試器數據)


我們看到總共有30007個draw調用,比統計面板報告的還要多,因為還有一些命令沒有被計數為批,比如清除目標緩衝區。在UpdateDepthTexture下為我們的點陣繪製的30000被單獨列出為Draw Mesh Point(Clone),RenderShadowMap,以及RenderForward.RenderLoopJob。


如果我們在啟用了動態批處理的情況下再次嘗試,那麼命令結構將保持不變,只是每組10000次Draw被減少為12次Draw動態調用。這是一個顯著的改進。

(DRP開啟了動態批處理)


如果我們使用GPU instancing,那麼每組將減少到20個Draw Mesh (Instanced) Point(Clone)調用。這也是一個很大的改進,但是方法不同。

(DRP開啟了GPU instancing)


我們可以看到,URP也會發生相同的情況,但是命令層次不同。在我的例子中,將繪製兩次點,首先在Render Main Shadowmap下,再在Render Opaques下。一個顯著的區別是,動態批處理似乎不適用於陰影貼圖,這解釋了為什麼它對URP的有效性較低。我們最終也得到了22個批處理,而不是12個批處理,這表明URP材質比標準DRP依賴更多的網格頂點數據,因此單個批處理中的點較少。與動態批處理不同,GPU實例化確實適用於陰影,因此在這種情況下它更出色。



(URP 分別再 不開啟任何優化, dynamic batching,和GPU instancing的表現)


最後,在啟用SRP的情況下,將10000點列為11個SRP Batch命令,但請記住,這些仍然是單獨的繪製調用,只是非常有效的調用。

(URP 開啟SRP batcher)


1.5 額外的燈光


到目前為止,我們得到的結果是針對我們的視圖的,帶有一個單一的方向光,以及我們使用的其他項目設置。讓我們看看當我們向場景中添加第二個燈光時,特別是通過GameObject/ Light / Point Light的點光源時會發生什麼。將其位置設置為零,並確保它不投射陰影,這是其默認設置。DRP支持點光源的陰影,但URP仍然不支持。

(原點上不帶陰影的點光源)


現在,有了額外的燈光,DRP繪製所有點需要更多的時間。幀調試器向我們展示了RenderForward.RenderLoopJob渲染的次數是以前的兩倍。更糟糕的是,動態批處理現在僅適用於深度和陰影通道,而不適用於前向通道了。


(DRP沒有和有動態批處理)


發生這種情況是因為DRP每個光源繪製一次每個對象。它具有一個主要通道,該通道與單個定向光源一起使用,然後在上面渲染其他通道。發生這種情況是因為這是一個老式的前向渲染通道。動態批處理無法處理這些不同的Pass,因此不會被使用。

對於GPU實例化也是一樣的,除了它仍然在主要通道上工作。額外的additional light passes 不能從中受益。

(DRP開啟GPU instancing)


但第二個光源對於URP似乎沒有影響,因為它是一種現代的前向渲染器,可一次應用所有光源。因此,即使GPU每次繪製需要執行更多的光照計算,命令列表仍保持不變。

這些結論對於影響所有點的單個額外的光來說是成立的。如果你添加更多的燈光並移動它們,不同的點會受到不同燈光的影響,事情就會變得更加複雜,當GPU實例化被使用時,批次會被分割。對於一個簡單的場景是正確的,對於一個複雜的場景可能並不正確。


延遲渲染呢?
DRP和HDRP都具有正向和延遲渲染模式,而URP當前沒有。延遲渲染的想法是對象被繪製一次,然後將其可見表面屬性存儲在GPU緩衝區中。此後,一個或多個燈光Pass,僅將照明應用於可見的區域。與正向渲染相比,它具有優點和缺點,但是在本教程系列中我們不會進行介紹。

1.6 Profiler


為了更好地了解CPU方面的情況,可以打開profiler窗口。關閉點光源,然後通過Window/ Analysis / Profiler打開窗口。 它將在播放模式下記錄性能數據並存儲以供以後檢查。


Profiler被分為兩個部分。它的頂部包含顯示各種性能圖的模塊列表。第一個是CPU使用率,這是我們將要關注的。選中該模塊後,窗口的底部將顯示我們可以在圖中選擇的幀的詳細分解。


(Profiler 顯示CPU使用的時間線,分別展示的是DRP和URP)


CPU使用率的默認底部視圖是時間線。它可以可視化在一個幀中花費了多少時間。它顯示了每個幀都以PlayerLoop開始,後者花費了大部分時間調用RunBehaviourUpdate。再往後兩步,我們看到這主要是對Graph.Update方法的調用。你可以選擇一個時間軸塊來查看其全名和持續時間(以毫秒為單位)。


在初始的player loop片段之後是一個簡短的EditorLoop部分,之後是另一個player片段,用於幀的渲染部分,CPU告訴GPU做什麼。工作在主線程、渲染線程和一些作業工作線程之間被分割,但是DRP和URP的具體方法不同。這些線程並行運行,但當一個線程必須等待另一個線程的結果時,它們也有同步點。


在渲染部分之後,當渲染線程仍然忙碌時,如果URP被使用,在下一幀開始會出現另一個編輯器段。

如果您對線程的確切時間不感興趣,則可以通過左側的下拉列表將Timeline視圖替換為Hierarchy視圖。層次結構在單個可排序列表中顯示相同的數據。通過此視圖,可以更輕鬆地查看花費時間最長的時間以及發生內存分配的位置。


1.7 分析一次構建


分析器很明顯地看出來,編輯器自身為應用程式增加了很多開銷。因此,在單獨運行我們的應用程式時,對它進行配置很有用。為此,我們需要構建我們的應用程式,專門用於調試。我們可以在「構建設置」窗口(通過File / Build Settings.... 打開)中配置應用的構建方式。如果尚未進行配置,則Scenes in Build 部分為空。這沒什麼問題,因為默認情況下將使用當前打開的場景。


你可以選擇目標平臺,但當前開發平臺上的一般最方便。然後啟用Development Build和Autoconnect Profiler選項。

(Development Build 模式)


要最終創建通常稱為構建的獨立應用程式,請按Build按鈕,或者在構建過程完成後,單擊Build and Run立即打開該應用程式。你也可以通過File / Build and Run或指示的快捷方式觸發另一個構建。

構建過程需要多長時間?
使用URP時,首次構建花費的時間最長,並且可能會忙幾分鐘。之後,如果可能,Unity將重用以前生成的構建數據,從而大大加快了該過程。除此之外,項目越大,花費的時間越長。

一旦構建自行運行,請過一會兒將其退出,然後切換回Unity。Profiler現在應包含有關其執行方式的信息。在首次構建後,這種情況並不總是會發生,如果是的話,請再試一次。還需要記住,即使啟用了Clear on Play功能,Profiler也不會清除附加到內部版本的舊數據,因此,如果僅運行應用程式幾秒鐘,請確保你正在查看相關的幀。


(分析構建後的版本 DRP和URP)


因為沒有編輯器開銷,所以Build之後的性能應比Unity編輯器中的播放模式更好。Profiler確實將不再顯示編輯器循環部分。在我的示例中,使用URP時,CPU現在還必需要等待VSync,這表明幀速率受顯示刷新率的限制。同樣,渲染線程似乎延伸到下一幀以進行URP。發生這種情況是因為Unity可以利用並行性在渲染線程完成之前啟動主線程上下一幀的更新循環。我們將在下一部分稍後再討論。


2 展示幀率


我們並不總是需要詳細的性能分析信息,通常只要大致了解一下幀速率即可。另外,我們(或其他人)可能在沒有Unity編輯器可用的地方運行我們的應用程式。對於這些情況,我們可以做的是在一個小的覆蓋面板中測量並在應用程式本身中顯示幀。此類功能默認情況下不可用,因此我們將自行創建。


2.1 UI面板


可以使用Unity的遊戲界面創建一個小的overlay面板。我們還將使用TextMeshPro創建文本以顯示幀頻。TextMeshPro是一個單獨的程序包,其中包含高級文本顯示功能,優於默認的UI文本組件。如果尚未安裝其軟體包,請通過軟體包管理器添加它。這也會自動安裝Unity UI軟體包,因為TextMeshPro依賴於它。


我們使用TextMeshPro創建文本以顯示幀頻。TextMeshPro是一個單獨的程序包,其中包含高級文本顯示功能,優於默認的UI文本組件。如果尚未安裝其軟體包,請通過軟體包管理器添加它。這也會自動安裝Unity UI軟體包,因為TextMeshPro依賴於它。

一旦UI包成為項目的一部分,就可以通過GameObject/ UI / Panel創建一個面板。這將創建一個覆蓋整個UI畫布的半透明面板。畫布與遊戲窗口大小匹配,但在場景窗口中更大。最簡單的方法是通過場景窗口工具欄啟用2D模式,然後進行縮小。

(面板覆蓋了畫布)


每個UI都有一個canvas根對象,它是在我們添加面板時自動創建的。面板是畫布的子元素。它創建了一個EventSystem遊戲對象,它負責處理UI輸入事件。我們不會使用這些,所以可以忽略甚至刪除它。

(UI遊戲對象層次)


畫布有一個scaler組件,可用於配置UI的比例。默認設置假設像素大小不變。如果你使用的是高解析度或視網膜顯示,那麼你就必須增加比例因子,否則UI就會太小。你還可以嘗試其他的縮放模式。

(UI Canvas 對象)


UI遊戲對象具有專門的RectTransform組件,該組件替代了常規的Transform組件。除了通常的位置,旋轉和縮放之外,它還顯示寬度,高度,樞軸和錨點。錨控制對象相對於其父對象的相對位置和大小調整行為。更改它的最簡單方法是通過單擊方形錨圖像打開的彈出窗口。

(UI Panel)


我們將幀速率計數器面板放在窗口的右上方,因此將面板的錨點設置在右上方。然後將寬度設置為38,將高度設置為70,將XY位置設置為這些尺寸的一半。另外,我們也可以在兩個維度上都將樞軸設置為1,然後將位置設置為零。然後將圖像組件的顏色設置為黑色,並保持其Alpha不變。

(右上角 深色的Panel)


2.2 Text


要將文本放入面板,請通過GameObject/ UI / Text-TextMeshPro創建一個TextMeshPro UI文本元素。如果這是你第一次創建TextMeshPro對象,則將顯示Import TMP Essentials彈出窗口。按照建議導入。這將創建一個TextMesh Pro資產文件夾,其中包含一些資產,我們無需直接處理。


創建文字遊戲對象後,使其成為面板的子節點,將其錨定為兩個方向的拉伸模式。這將用右側和底部欄位替換寬度和高度。現在,使其與整個面板重疊,這可以通過將left,top,right和bottom設置為零來完成。還要給它一個描述性名稱,例如Frame Rate Text。

(UI Text)


接下來,對TextMeshPro-文本(UI)組件進行一些調整。將Font Size設置為14,將Alignment設置為居中居中。然後用佔位符文本(特別是FPS)填充文本輸入區域,然後是三行,每行三個零。

(Text 設置)


現在,我們可以看到幀速率計數器的外觀。三行顯示為0的就是我們稍後將顯示的統計信息的佔位符。

(Frame rate text)


2.3 更新顯示


要更新計數器,我們需要一個自定義組件。為FrameRateCounter組件創建一個新的C#腳本資產。給它一個可序列化的TMPro.TextMeshProUGUI欄位,以保存對用於顯示其數據的文本組件的引用。

將此組件添加到文本對象並連接顯示。

(幀率計數器組件)


要顯示幀速率,我們需要知道前一幀和當前幀之間經過了多少時間。可通過Time.deltaTime獲得此信息。但是,此值受可用於時間調整(例如時間停止或項目符號時間)的時間刻度的限制。我們需要改用Time.unscaledDeltaTime。在FrameRateCounter中新的Update方法開始時對其進行檢索。

下一步是調整顯示的文本。我們可以通過使用文本字符串參數調用其SetText方法來做到這一點。提供與我們已有的相同的佔位符文本。在雙引號之間寫入一個字符串,並使用特殊的\ n字符序列寫入一個換行符。

TextMeshProUGUI具有各種SetText方法,這些方法可以接受附加的float參數。將幀持續時間添加為第二個參數,然後在大括號內將字符串的第一個三零行替換為一個零。這表明應該在字符串中插入float參數的位置。

幀持續時間告訴我們經過了多少時間。為了顯示幀速率表示為每秒幀數,我們必須顯示其倒數,因此將其除以幀持續時間。

這將顯示一個有意義的值,但是它將有很多數字,例如59.823424。我們可以指示文本四捨五入到小數點後的特定位數,方法是在零後面加上顏色和所需的數字。我們將捨入為整數,所以加零。

(顯示上一幀的幀率)


2.4 平均幀率


由於連續幀之間的時間幾乎永遠不會完全相同,因此顯示的幀速率最終會迅速變化。通過顯示平均幀速率而不是僅顯示最後一幀的速率,可以減少不穩定現象。通過跟蹤已渲染的幀數和總持續時間,然後顯示幀的數量除以它們的合併持續時間,可以做到這一點。

這將使我們的幀率趨勢變為運行時間越長,越趨向於穩定的平均值,但是該平均值適用於我們應用的整個運行時間。由於我們需要最新的信息,因此我們必須重新設置並重新開始,並採樣新的平均值。可以通過添加可序列化的採樣持續時間欄位(默認設置為一秒鐘)來使其可配置。給它一個合理的範圍,例如0.1–2。持續時間越短,我們得到的結果就越精確,但是隨著變化速度的加快,將會變的很難理解。


(採樣時間設定為1秒)


從現在開始,我們僅在累計持續時間等於或超過配置的採樣持續時間時調整顯示。我們可以使用>=大於等於運算符進行檢查。更新顯示後,將累積的幀和持續時間設置回零。

(1秒的平均幀率)


2.5 最好和最差


平均幀率波動是因為我們的應用程式的性能不是恆定不變的。有時它會變慢,這是因為它暫時有更多工作要做,或者是因為同一臺計算機上運行的其他進程妨礙了它。為了了解這些波動有多大,我們還將記錄並顯示在採樣期間發生的最佳和最差幀持續時間。默認情況下,將最佳持續時間設置為float.MaxValue,這是最壞的最佳持續時間。

每次Update都會檢查當前幀持續時間是否小於到目前為止的最佳持續時間。如果是,則使其成為新的最佳持續時間。還要檢查當前幀持續時間是否大於迄今為止最差的持續時間。如果是這樣,則使其成為新的最差持續時間。

現在,我們將最佳幀速率放在第一行,將平均幀放在第二行,將最差幀速率放在最後一行。通過向SetText添加兩個額外參數並向字符串添加更多佔位符來實現。它們是索引,因此第一個數字以0表示,第二個數字以1表示,第三個數字以2表示。此後,還重置最佳和最差持續時間。

(最好、平均和最差幀率)


請注意,即使啟用了VSync,最佳幀率也可能超過顯示刷新率。同樣,最壞的幀速率不必一定是顯示刷新速率的倍數。這是可能的,因為我們不是測量顯示的幀之間的持續時間。而是在測量Unity幀之間的持續時間,這是其更新循環的區間迭代。


Unity的Update循環無法與顯示器完美同步。當Profiler顯示當前幀的渲染線程仍在忙時,下一幀的播放器循環開始時,我們已經看到了提示。渲染線程完成後,GPU仍有一些工作要做,此後仍需要一些時間才能刷新顯示。因此,我們顯示的FPS不是真實的幀速率,而是Unity告訴我們的。理想情況下,這些是相同的,但是正確處理是複雜的。

有一篇關於Unity如何在這方面改進的博客文章,但這並沒有講述完整的內容。
https://blogs.unity3d.com/2020/10/01/fixing-time-deltatime-in-unity-2020-2-for-smoother-gameplay-what-did-it-take/


2.6 幀持續時間


每秒幀數是衡量感知性能的一個很好的單位,但是當嘗試達到目標幀速率時,顯示幀持續時間會更有用。例如,當嘗試在行動裝置上實現穩定的60FPS時,每個毫秒都非常重要。因此,我們將顯示模式配置選項添加到我們的幀頻計數器中。

在FrameRateCounter中為FPS和MS定義一個DisplayMode枚舉,然後添加該類型的可序列化欄位,默認情況下設置為FPS。


(可配置的顯示模式)


然後,當我們在Update中刷新顯示時,請檢查模式是否設置為FPS。如果是,請執行我們已經在做的事情。否則,將FPS標頭替換為MS並使用反參數。將它們也乘以1000,即可將秒數轉換為毫秒數。

(單幀最好、平均和最差的毫秒)



幀持續時間通常以十分之一毫秒為單位。我們可以通過將數字捨入從零增加到1來將顯示精度提高一級。


(更高的精度)


2.7 內存分配


我們的幀頻計數器已經完成,但是在繼續之前,我們先檢查一下它對性能的影響。顯示UI需要每幀更多的繪製調用,但實際上並沒有什麼不同。在播放模式下使用profiler,然後搜索我們在其中更新文本的幀。事實證明,這並不需要很多時間,但是它確實分配了內存。通過層次結構視圖按GC Alloc列排序最容易檢測到。

(內存分配情況)


文本字符串是對象。當我們通過SetText創建一個新的字符串時,這將產生一個新的字符串對象,該對象負責分配48個字節。然後,Unity的UI刷新將其增加到5 KB。儘管數量不多,但它會累積,在某個時候觸發內存垃圾回收過程,這將導致不希望的幀持續時間尖峰。


注意臨時對象的內存分配並儘可能地消除重複出現的對象是很重要的。幸運的是,因為各種原因,SetText和Unity的UI update只在編輯器中執行這些內存分配,比如更新文本輸入欄位。如果我們對一個Build進行剖析,那麼我們將不會發現這些分配。所以這是建立概要文件的必要條件。編輯器播放模式下的性能分析只對第一印象好。


3 自動進行函數切換


現在,我們知道了如何分析應用程式,我們可以在顯示不同功能時比較其性能。如果某個功能需要更多的計算,則CPU必須做更多的工作,從而降低幀速率。儘管如何計算對GPU沒有影響。但如果解析度相同,GPU將必須執行相同的工作量。


wave 和torus功能之間的最大區別是CPU的使用率,我們可以通過分析器比較它們的差別。我們可以比較配置了不同功能的兩個單獨的運行,也可以在播放模式下進行配置文件並在播放期間進行切換。

(從torus 到wave的切換出現了峰值)


CPU圖顯示,從圓環切換為波浪形後,負載確實減小了。切換發生時,還會出現巨大的幀持續時間尖峰。發生這種情況的原因是,通過編輯器進行更改時,播放模式會暫時暫停。由於取消選擇和編輯器焦點更改,後來也出現了一些其他峰值。


峰值屬於另一種類型。通過切換左側的類別標籤,可以過濾CPU圖,這樣我們只能看到相關的數據。禁用另一個類別時,計算量的變化更明顯。

(其他的種類,沒有展示)


由於暫停,通過檢查器進行的切換功能很難進行配置。更糟糕的是,我們必須重新構建一個新的版本來分析單獨的功能。我們可以通過自動或通過用戶輸入通過其檢查器添加將功能切換到圖形的功能來改進此功能。我們將在本教程中選擇第一個選項。


3.1 函數循環


我們的想法是讓所有功能自動循環。每個功能將顯示固定的時間,此後將顯示下一個功能。要使功能持續時間可配置,請為其在Graph上添加一個可序列化的欄位,默認值為一秒鐘。還可以通過為其賦予Min屬性來將其最小值設置為零。持續時間為零將導致每幀切換到不同的功能。


(函數持續時間)


從現在開始,我們需要跟蹤當前功能的激活時間,並在需要時切換到下一個功能。這會使我們的Update方法複雜化。它的當前代碼僅用於更新當前函數,因此讓我們將其移至單獨的UpdateFunction方法,並讓Update調用它。這樣可以使我們的代碼井井有條。

現在,添加一個持續時間欄位,並在更新開始時將其增加(可能是按比例縮放的)增量時間。然後,如果持續時間等於或超過配置的持續時間,則將其重置為零。之後是UpdateFunction的調用。

我們很可能永遠不會完全達到功能持續時間,我們會稍微超過它一點。可以忽略這一點,但是要與功能開關的例外時序保持合理的同步,應該從下一個功能的持續時間中減去額外的時間。我們通過從當前持續時間中減去所需的持續時間而不是將其設置為零來實現。

為了遍歷函數,我們將在FunctionLibrary中添加GetNextFunctionName方法,該方法採用一個函數名稱並返回下一個。由於枚舉是整數,因此我們可以在其參數中加一個並返回它。

但是我們還需要循環回第一個函數才行,否則,當移到最後一個函數在循環時,將得到一個無效的名稱。因此,僅當提供的名稱小於枚舉數時,我們才可以增加它。否則,我們將返回第一個函數,即wave。可以使用if-else塊來執行此操作,每個塊都返回適當的結果。

通過將名稱(以int形式)與函數數組的長度減去一個(與最後一個函數的索引匹配)的長度進行比較,可以使該方法與函數名稱無關。如果最後我們也可以返回零,這是第一個索引。這種方法的優點是,如果以後更改函數名稱,則無需調整方法。

也可以通過使用?:三元條件運算符將方法主體簡化為單個表達式。這是帶有-的if-then-else表達式。和:分離各部分。兩種選擇都必須產生相同類型的值。

在適當的時候使用Graph.Update中的新方法切換到下一個函數。

(函數循環)


現在,我們可以通過對build進行概要分析來依次查看所有功能的性能。

(對循環函數進行Profile)


在我的例子中,所有函數的幀速率都是一樣的,因為它從不低於60FPS。通過等待垂直同步來消除這些差異。隱藏VSync可以使函數的不同加載更容易在圖中看到。

(垂直同步關閉)


事實證明,Wave最快,其次是Ripple,然後是Multi Wave,其次是Sphere,而Torus最慢。我們有代碼,這符合我們的期望。


3.2 隨機函數


讓我們通過添加一個在函數之間隨機切換而不是循環固定序列的選項來使我們的圖更有趣。將一個GetRandomFunctionName方法添加到FunctionLibrary中以支持此方法。它可以通過調用零的Random.Range和函數數組長度作為參數來選擇隨機索引。選擇的索引是有效的,因為這是方法的整數形式,為此提供的範圍是包含所有值的範圍。

我們可以更進一步,確保我們永遠不會連續兩次獲得相同的功能。為此,將我們的新方法重命名為GetRandomFunctionNameOtherThan並添加一個函數名稱參數。將Random.Range的第一個參數增加為1,因此永遠不會隨機選擇索引零。然後檢查選擇是否等於要避免的名稱。如果是這樣,則返回名字,否則返回所選名字。因此,我們用零代替了不允許的索引,而沒有引入偏差的方式。

返回到Graph,為過渡模式添加配置選項,可以是循環或隨機的。再次使用自定義枚舉欄位執行此操作。

選擇下一個功能時,請檢查轉換模式是否設置為循環。如果是這樣,則調用GetNextFunctionName,否則調用GetRandomFunctionName。因為這會使選擇下一個函數變得複雜,所以我們也將這段代碼放在一個單獨的方法中,以使Update保持簡單。

(選擇隨機函數)


3.3 函數插值


我們通過使功能之間的過渡更加有趣來結束本教程。無需突然切換到另一個函數,我們就可以將圖形平滑地變形為下一個。這對於性能分析也很有趣,因為它需要在過渡期間同時計算兩個函數。


首先在FunctionLibrary中添加一個Morph函數,該函數將負責過渡。為它提供與函數方法相同的參數,外加兩個Function參數和一個float參數以控制變形進度。

我們使用Function參數而不是FunctionName參數,因為這樣Graph可以在每次更新時按名稱檢索一次函數,因此我們不必每個點訪問兩次函數數組。


為什麼要在Graph檢索中每個Update Graph的函數?
我們也可以將函數存儲在Graph的欄位中,而不用獲取每次更新。我們之所以不這樣做,是因為Function類型的欄位值不能在熱重載中生存,而FunctionName欄位卻可以。而且,每次更新檢索一個或兩個功能不會對性能產生有意義的影響。但是,每次更新每個點都要這樣做,這會帶來很多不必要的額外工作。

進度是一個0–1的值,我們將使用它來從第一個提供的函數插入到第二個函數。我們可以為此使用Vector3.Lerp函數,將兩個函數的結果和進度值傳遞給它。

Lerp是線性插值的縮寫。它將在兩個函數之間產生一個直線的恆速轉換。我們可以通過放慢開始和結束的進度來讓它看起來更流暢一些。這是通過將原始進程替換為對Smoothstep的調用,使用0、1和progress作為參數來實現的。它應用了函數,通常稱為平滑步長。平滑步長的前兩個參數是這個函數的偏移量和比例,我們不需要它,所以用0和1。

(0~1平滑步長VS線性)


Lerp方法限制了它的第三個參數,因此它在0–1範圍內。Smoothstep方法也可以做到這一點。我們將後者配置為輸出0–1的值,因此不需要額外的Lerp鉗位。對於這種情況,有另一種LerpUnclamped方法,所以我們改用它。


3.4 過渡


函數之間的過渡期需要一個持續時間,因此請為它添加一個配置選項到Graph,並且最小和默認值與函數持續時間相同。


(過渡持續時間)


現在,我們的圖形可以處於兩種模式,即過渡與否。我們將使用布爾類型的布爾型欄位來跟蹤此情況。我們還需要跟蹤要轉換的函數的名稱。

UpdateFunction方法用於顯示單個功能。複製它,並將新的命名為UpdateFunctionTransition。對其進行更改,使其同時獲得兩個功能並計算進度,即當前持續時間除以過渡持續時間。然後讓它調用Morph而不是在其循環中調用單個函數。

最後,請檢查我們是否正在過渡。如果是這樣,則調用UpdateFunctionTransition,否則調用UpdateFuction。

一旦持續時間超過了function duration時間,我們就進入下一個持續時間。在選擇下一個函數之前,請先說明我們正在過渡,並使過渡函數等於當前函數。

但是,如果我們已經在過渡,則必須做其他事情。因此,首先檢查我們是否正在過渡。只有在這種情況下,才需要檢查是否超過了功能持續時間。

如果要過渡,則必須檢查是否超過過渡持續時間。如果是這樣,請從當前持續時間中減去過渡持續時間,然後切換回單功能模式。

(不同函數之間的過渡)


現在,如果我們進行概要分析,我們可以看到確實在過渡期間Graph.Update需要花費更長的時間。究竟需要多少時間取決於它在兩個功能之間的融合。


(Profiler構建顯示過渡的額外工作,有和沒有垂直同步)


需要重申的是,你獲得的性能分析結果取決於你的硬體,並且可能與我在本教程中顯示的示例完全不同。在開發自己的應用程式時,請確定你支持哪些最低硬體規格並通過這些最低規格進行測試。你的開發機器僅用於初步測試。如果要針對多個平臺或硬體規格,則需要多個測試設備。

下一章節 計算著色器。


歡迎掃描二維碼,查看更多精彩內容。點擊 閱讀原文 可以跳轉原教程。


本文翻譯自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials

相關焦點

  • Android Systrace 基礎知識(3) - Why 60 fps ?
    本文是 Systrace 系列文章的第三篇,解釋一下為何大家總是強調 60 fps。
  • Galaxy S20系列如何開啟120fps遊戲和打開fps懸浮窗?
    Galaxy S20系列擁有QHD+120Hz屏幕(可惜不能同時開啟),由於120Hz對於視覺上的流暢度提升很大,相信很多用戶都會優先選擇開啟FHD+ 120Hz的模式,在部分遊戲中可以支持最高120fps的高幀率模式,例如和平精英。
  • Unity協程性能分析
    但是在使用它之前,我們需要了解它的性能消耗。今天我們將分析協程的啟動消耗和執行消耗。協程就是C#中的迭代器函數。這意味著它返回System.Collections.IEnumerator並且函數體中至少有一個yield return X語句。
  • Unity基礎教程系列(新)(三)——數學表面(Sculpting with Numbers)
    本教程是CatLikeCoding系列的一部分,原文地址見文章底部。本教程使用Unity 2019.4.10f1製作。通過轉到下面的Shadows部分並將Shadow Distance減小為10,並將Shadow Cascades設置為No Cascades,我們可以進一步調整陰影的性能和精度。默認設置最多可渲染四次陰影,這對我們來說是過大了。
  • 【新教學上架】Unity遊戲特效案例系列教學
    Unity遊戲特效案例系列教學本教學由TAK老師錄製,通過四個有代表性的案例,教授作者行業多年的遊戲特效製作流程和經驗
  • 基於新型儀器精確測量EVM的射頻放大器性能詳細分析
    在最新的通信系統協議中,分析功率放大器性能最重要的測量就是測量誤差矢量幅值,即EVM。它衡量的是調製的精度,即功率放大器傳輸由不同相位和幅值的射頻信號表示的信息的優劣。通過EVM測量能夠觀察到通信鏈路內部的情況,是衡量發射器性能的關鍵。在接收器一側,EVM衡量的是接收器解調傳輸信號的優劣。
  • 【厚積薄發】Unity Batches與glDrawElements的關係
    以後如果有對比兩種方案的性能,我再進行補充。, not a strong GPU), 50~60fps, performance mainly affected by visible grass count on screen(draw distance = 125)can handle 10 million instances on Lenovo S5 (GPU = adreno506, a weak GPU), 30fps,
  • 面試官問我:Android APP中如何測試FPS?看我如何分析京東,拼多多App的FPS.
    這部分佔用的時間通常比較少而prepare在一些舊adb版本中是算在process上,就是指從創建顯示列表到執行顯示列表的這一段準備時間的開銷Draw + Prepare+Process + Execute = 完整顯示一幀 ,這個時間要小於16ms才能保證每秒60幀,即fps為60,才不會出現卡頓。
  • 強勁性能表現,助力華為nova8 Pro首發《王者榮耀》90fps高幀模式
    比如說,目前高刷手機的市場份額快速增長,連一些二三線手遊都已經適配高刷新率,《王者榮耀》作為一線手遊顯得有點跟不上節奏,最高幀率一直維持在 60fps。但如今全新「破曉」版本的宣布,意味著《王者榮耀》終於要開啟 90fps 高幀率的大門。而首款適配《王者榮耀》 90fps 高幀率的機型,就是不久前發布的華為nova8 Pro。
  • 幾乎是史上最全最實用的Android性能全面分析與優化方案研究
    結合以下四個部分講解:性能問題分類性能優化原則和方法
  • 諾曼 houdini免費基礎教程 !目錄! 免費獲得價值3880元的基礎教程
    ,我喜歡結合17年houdini使用經驗,以及7年好萊塢電影製作經驗來錄製基礎教程(2018 水型物語 獲得2018奧斯卡最佳影片獎),並且包含「出錯後如何處理系列視頻」 幫助houdini 新人學習houdini過程中解決問題
  • 遊戲中的FPS是什麼?FPS越高越好還是越低越好?
    FPS 是測量用於保存、顯示動態視頻的信息數量。每秒鐘幀數愈多,所顯示的動作就會愈流暢。就像在玩吃雞或其他第一人稱遊戲時,滑鼠轉動時畫面會變化,這些畫面的生成就與FPS有關。電影以每秒24張畫面的速度播放,也就是一秒鐘內在屏幕上連續投射出24張靜止畫面。
  • 【厚積薄發】DrawInstance和完全不做合批情況下的性能差異
    Default:子線程Camera.Render(14.59 ms)、Gfx.PresentFrame(3.94 ms)Instancing:子線程Camera.Render(7.1 ms)、Gfx.PresentFrame(4.70 ms)
  • 引爆極限性能 實測華碩ROG系列頂級Z68
    華碩ROG系列主板一直是發燒友的的最愛,頂級的做工,奢華的品質,再加上極限性能表現,無不讓每個玩家都心動不已。隨著Z68晶片組的推出,華碩也推出了旗下的ROG產品,型號為Maximus IV Extreme-Z。該主板秉承了ROG的設計元素,帶給玩家頂級的性能享受。
  • Google 發布 Android 性能優化典範 - OSCHINA - 中文開源技術交流...
    如果你的某個操作花費時間是24ms,系統在得到VSYNC信號的時候就無法進行正常渲染,這樣就發生了丟幀現象。那麼用戶在32ms內看到的會是同一幀畫面。每一條柱狀線都包含三部分,藍色代表測量繪製Display List的時間,紅色代表OpenGL渲染Display List所需要的時間,黃色代表CPU等待GPU處理的時間。4)Why 60fps?
  • 南方Cass全系列教程,視頻+插件+測量表全打包
    南方Cass全系列教程,視頻+插件+測量表全打包南方Cass,工程人應該不陌生!Cass軟體是一套集地形、地籍、空間數據建庫、工程應用、土石方算量等功能為一體的軟體系統。近年來,這一軟體應用領域不斷擴大,其中主要應用於地形成圖、地藉成圖、工程測量應用三大領域,是工程人提高工作效率必不可少的繪圖軟體。
  • 性能、功能的爆炸式翻倍增長——GeForce RTX 30系Ampere架構GPU...
    NVIDIA宣稱全新的顯卡帶來了更多的高級功能、更強大的性能和更超值的價格。由於新的RTX 30系列整體表現超出玩家預期,並且定價相對更為合理,很快就引發了市場聚焦式的關注。為了更清楚地向大家介紹全新的RTX 30系列顯卡,本文將從多個方面、多個角度為大家帶來針對Ampere核心的架構、產品及技術上的深入分析和介紹。
  • 基於皮秒級時間間隔測量的集成電路和系統解決方案---TDC
    具體來講,TDC是以信號通過內部門電路的傳播延遲來進行高精度時間間隔測量的,如下圖1顯示了這種測量絕對間隔時間TDC的主要框架。晶片上的智能電路結構、冗餘電路和特殊的布線方法使得晶片可以精確地記下信號通過門電路的個數,並且能保證每個門電路的延遲時間嚴格一致。晶片能獲得的最高測量精度由信號通過晶片內部門電路的最短傳播延遲時間tpd決定。
  • 隧道防水滲漏——橡膠密封材料力學性能測量
    在我國,上海地鐵四號線、杭州地鐵四號線、福州地鐵一號線、佛山地鐵二號線等多地多線亦曾出現過透水沉降事件。隧道防滲漏和普通民眾的生命安全密切相關,透水事故給我們隧道工程的建設敲響警鐘,隧道防滲漏技術和材料性能的研究需要高度重視。