轉(zhuǎn)自:【Unity】渲染性能優(yōu)化---經(jīng)驗(yàn)總結(jié)(一) - 嗶哩嗶哩 (bilibili.com)
1、渲染優(yōu)化的幾大性能點(diǎn)
我們來簡(jiǎn)單瀏覽一下渲染的主要過程:
CPU計(jì)算和收集渲染所需數(shù)據(jù)組裝描述符和材質(zhì)--->CPU向GPU傳遞渲染所需數(shù)據(jù)--->CPU發(fā)起DrawCall--->GPU進(jìn)行渲染和計(jì)算--->在一些情況下GPU會(huì)向CPU回傳數(shù)據(jù)(例如一些ComputeShader)
這里首先引出一個(gè)關(guān)鍵點(diǎn):渲染流程中實(shí)際上大部分階段都是需要CPU參與的!!!!?有時(shí)程序在定位性能熱點(diǎn)時(shí),覺得項(xiàng)目中沒有多少GC和復(fù)雜運(yùn)算,那么性能問題就一定是發(fā)生在GPU上,實(shí)際上不然。就我個(gè)人而言,不論是PC、主機(jī)還是移動(dòng)端的GPU,其性能我還是比較自信的,如果沒有什么騷操作,GPU中的運(yùn)算通常不會(huì)引發(fā)嚴(yán)重的性能問題。
那么根據(jù)上面的渲染流程,也就可以得出渲染中的幾大性能點(diǎn):
1、對(duì)于 CPU計(jì)算和收集渲染所需數(shù)據(jù)組裝描述符和材質(zhì) 階段:項(xiàng)目中存在大量零散瑣碎的物體;有大量可以共用材質(zhì)的物體卻沒有共用材質(zhì);有大量復(fù)雜的動(dòng)畫運(yùn)算和蒙皮運(yùn)算等,這些都會(huì)使CPU計(jì)算和收集渲染數(shù)據(jù)的時(shí)間延長(zhǎng)。
2、對(duì)于 CPU向GPU傳遞渲染所需數(shù)據(jù) 階段:該階段產(chǎn)生的性能問題就是常說的 帶寬問題,CPU 與 GPU 用于傳遞數(shù)據(jù)的通道,其傳輸速率有限(而這個(gè)傳輸速率就稱為帶寬),當(dāng)一幀內(nèi)傳輸?shù)膬?nèi)容大小大于一幀的傳輸速率時(shí),就會(huì)出現(xiàn)傳輸排隊(duì),導(dǎo)致后續(xù)渲染延遲。 尤其對(duì)于移動(dòng)端,由于移動(dòng)端的 CPU 和 GPU 間的帶寬本來就較小,且移動(dòng)端 CPU 和 GPU 都在一塊芯片上,共用一整塊功率,當(dāng)出現(xiàn)帶寬問題時(shí),使用功率上升,導(dǎo)致手機(jī)發(fā)熱較快。?貼圖占用內(nèi)存過大、網(wǎng)格數(shù)據(jù)占用內(nèi)存過大、一些大物體在CPU端視錐體檢測(cè)無法被篩掉等,這些都會(huì)觸發(fā)帶寬問題。
3、對(duì)于?CPU發(fā)起DrawCall 階段:這一階段導(dǎo)致性能問題的就是 DrawCall 的數(shù)量 和 DrawCall 本身的復(fù)雜度了。DrawCall 就是 CPU 的一種指令,所以其耗時(shí)就是本身指令執(zhí)行的耗時(shí)。通常我們認(rèn)為渲染在GPU上有性能問題時(shí),往往最終是由于 DrawCall 數(shù)過多導(dǎo)致GPU開始渲染的時(shí)間節(jié)點(diǎn)被大幅延遲導(dǎo)致的。該合批的沒有合批、該用同一個(gè)材質(zhì)的沒有用同種材質(zhì)、美術(shù)資源的制作上導(dǎo)致合批困難、材質(zhì)有大量的屬性和變量等,都會(huì)導(dǎo)致 DrawCall 數(shù)量的上升 和 DrawCall本身指令的復(fù)雜化。
4、對(duì)于 GPU進(jìn)行渲染和計(jì)算 階段:這一階段才算徹底的走到了GPU的內(nèi)部,也就是我們覺得要改Shader的時(shí)候。但實(shí)際上,這一階段出現(xiàn)的一些問題也不是需要簡(jiǎn)化Shader運(yùn)算才能解決的。這一階段會(huì)產(chǎn)生性能問題的主要原因有:渲染順序的不合理和特效面片的不合理導(dǎo)致過多的Overdraw、Shader中冗余或復(fù)雜的計(jì)算、貼圖或網(wǎng)格數(shù)據(jù)過大導(dǎo)致運(yùn)算時(shí)加載數(shù)據(jù)過慢、對(duì)于移動(dòng)端 一些騷操作或者不合理的渲染流程還會(huì)觸發(fā) TileBase 架構(gòu)的 GMEM_Load 操作導(dǎo)致渲染過程變慢。
5、對(duì)于 在一些情況下GPU會(huì)向CPU回傳數(shù)據(jù) 階段:通常游戲開發(fā)中不會(huì)遇到這一階段。對(duì)于這一階段我們只需要留意使用 GPU 做計(jì)算功能時(shí),其結(jié)果應(yīng)該盡可能的是留在GPU的內(nèi)存中,作為GPU后面計(jì)算或渲染的輸入,如果一定要把結(jié)果傳給CPU,那就要注意結(jié)果的大小,避免產(chǎn)生帶寬問題。
2、如何確定渲染性能點(diǎn)是在CPU還是GPU?
通過上面我們知道,對(duì)于渲染流程有的過程是在CPU、有的過程是在GPU,因此渲染的性能點(diǎn)也是有的在CPU、有的在GPU。那么究竟怎么確定到底是CPU的問題還是GPU的問題呢?
這里我推薦的是使用 Unity 自帶的性能分析器:Profiler (注意:如果游戲目標(biāo)平臺(tái)不是PC那么 Profiler 一定要是真機(jī)測(cè)試!!!)
Profiler 的 Timeline 視圖展示了每一幀中 CPU?和 GPU 進(jìn)行的主要階段。在一幀中,大致的流程為:CPU執(zhí)行一系列運(yùn)算后確定動(dòng)畫、網(wǎng)格和材質(zhì)信息--->CPU發(fā)起DrawCall--->GPU收到DrawCall和數(shù)據(jù)開始渲染--->在GPU渲染的同時(shí) CPU 可繼續(xù)向后運(yùn)行
那么如果在 CPU 發(fā)起 DrawCall 之前,GPU 沒有正在運(yùn)行的渲染任務(wù),那么GPU就會(huì)處在等待狀態(tài)。而如果 CPU 在發(fā)起 DrawCall 時(shí),GPU還有渲染任務(wù)沒有處理完,那么 CPU 就會(huì)處在等待狀態(tài),等待GPU將當(dāng)前任務(wù)完成后再發(fā)起DrawCall。(這里是一種簡(jiǎn)單的說法,對(duì)于 Vulkan 和 DX12 這些現(xiàn)代圖形API情況會(huì)有些不同,但其實(shí)也是大同小異)
對(duì)于等待的階段,Profiler 會(huì)用 灰色塊 表示:
圖2.1 灰色塊 Gfx.WaitForGfxCommandsFromMainThread 表示當(dāng)前GPU正在等待CPU
圖2.2 灰色塊 Semaphore.WaitForSignal 表示當(dāng)前 CPU 正在等待 GPU
那么如果 GPU 等待 CPU 的 灰色塊(如上圖2.1?渲染線程的 Gfx.WaitForGfxCommandsFormMainThread),所占時(shí)間過長(zhǎng),那就說明 CPU 在渲染階段運(yùn)行時(shí)間過長(zhǎng),渲染性能點(diǎn)出現(xiàn)在 CPU 端。
而如果 當(dāng)前幀內(nèi) 渲染線程(RenderThread) 的開頭出現(xiàn)了灰綠色塊(如圖2.2 渲染線程開頭的灰綠色塊) 且自身的綠色塊也很長(zhǎng)一直到排到最后;或者出現(xiàn)了CPU等待GPU的灰色塊(如圖2.2 的 Semaphore.WaitForSignal) 這就說明 GPU 在渲染時(shí)花費(fèi)了大量時(shí)間,甚至在一幀的時(shí)間內(nèi)都沒有處理完任務(wù),一直到下一幀還在處理。 也就是說渲染性能點(diǎn)出現(xiàn)在了 GPU 端。
這里也引出了 Profiler 的一個(gè)迷惑人的點(diǎn):當(dāng) CPU 等待 GPU 時(shí),Profiler 會(huì)一直拉長(zhǎng) CPU 當(dāng)前所處階段的時(shí)間。例如圖2.2,Semaphore.WaitForSingle 上面的?MeshSkinning.Update ,其顯示執(zhí)行了 7.33ms 但實(shí)際上它的執(zhí)行可能只花了 0.2ms,而之后都是在等待 GPU 任務(wù)的完成。因此當(dāng)我們發(fā)現(xiàn) CPU 某些任務(wù)執(zhí)行時(shí)間莫名過長(zhǎng)時(shí),要檢查一下其下面是否有 Semaphore.WaitForSignal 這個(gè)灰色塊,如果有就說明不是CPU執(zhí)行這項(xiàng)任務(wù)過慢,而是GPU出現(xiàn)了渲染性能問題。
3、CPU端渲染性能熱點(diǎn)確定
知道了是CPU還是GPU的問題后我們就來進(jìn)一步的確定問題。
對(duì)于CPU端的渲染性能點(diǎn),其實(shí)只要資源規(guī)范設(shè)定好,資源創(chuàng)作流水線定制好,渲染流水線設(shè)定好,那么就不會(huì)有太大的問題。
主要是多大的貼圖會(huì)產(chǎn)生帶寬問題?多少個(gè)DrawCall會(huì)造成明顯卡頓?多少個(gè)多少幀的動(dòng)畫或者多少個(gè)多少骨骼的角色會(huì)造成明顯卡頓?這些定量的問題,我還都沒有具體的去研究過,平常也沒有去記錄相關(guān)的數(shù)據(jù)。所以就算拿來一份指標(biāo)報(bào)告,你讓我看著這些數(shù)據(jù)也很難直接的確定出問題 X...X
所以對(duì)于這一塊我的想法就是做好資源管理和規(guī)范,盡量避免出現(xiàn)問題,真出現(xiàn)問題了就用排除法,其它地方?jīng)]問題,那一定就是這里有問題嘍。 所以在后面的資源管理章節(jié)我會(huì)再細(xì)說一這部分。
當(dāng)然,對(duì)于這一塊性能熱點(diǎn)的確定我也不是完全沒有辦法,這里我還是推薦使用 Untiy 的 Profiler,其 Timeline 把每個(gè)階段的耗時(shí)都標(biāo)出來了,我們只需要結(jié)合經(jīng)驗(yàn)看看那一塊的用時(shí)過高就可以確定問題所在啦。
Profiler 顯示的 動(dòng)畫計(jì)算用時(shí)
4、GPU端渲染性能熱點(diǎn)確定
這一部分首先根據(jù)自身經(jīng)驗(yàn)和直覺,有些東西是可以直接定位出來的。 如當(dāng)我們看到項(xiàng)目里有大量特效疊加,且特效面片很大時(shí),就會(huì)知道 GPU 渲染時(shí)可能產(chǎn)生了大量的 Overdraw,之后在用 Unity 自身的 Overdraw 窗口檢查一下,就知道當(dāng)前 Overdraw 是否需要優(yōu)化了。
在Unity內(nèi)檢測(cè)Overdraw情況,越紅表示Overdraw越高。圖中的其實(shí)不算太高,因?yàn)轫?xiàng)目這里我已經(jīng)優(yōu)化過一波了
而對(duì)于渲染順序引起的Overdraw,如果能和美術(shù)制定好流程規(guī)范,讓美術(shù)能夠理解 渲染隊(duì)列、Early-Z、Overdraw 這些規(guī)范那就會(huì)非常Nice,不然的話就是在美術(shù)制作好場(chǎng)景后自己檢測(cè)一遍然后做修改咯。
而對(duì)于其它直觀上難以確定的 GPU 性能點(diǎn),這里如果是移動(dòng)端我推薦使用 SnapdragonProfiler 進(jìn)行抓幀分析,其它平臺(tái)推薦 RenderDoc。SnapdragonProfiler 是高通出的性能分析器,因此其只有搭配使用高通芯片的手機(jī)才能完全的發(fā)揮它的作用,這里推薦小米和三星的手機(jī)。
SnapdragonProfiler
RenderDoc
關(guān)于怎么使用 SnapdragonProfiler 來確定 GPU 端 具體的性能點(diǎn),我會(huì)放在后面的抓幀工具篇細(xì)說。而至于 RenderDoc 由于我實(shí)際上用的不多,所以就先不談了。
造成渲染性能點(diǎn)的原因以及如何解決
1、CPU計(jì)算和收集渲染所需數(shù)據(jù)組裝描述符和材質(zhì) 階段的性能點(diǎn)
物體的合批。如果場(chǎng)景中存在大量的瑣碎的物體,那么 CPU 在做視錐體剔除 和 收集這些物體的渲染信息時(shí)的耗時(shí)就會(huì)增加。對(duì)于合批通常使用這三種方法:建模時(shí)就手動(dòng)合并網(wǎng)格、靜態(tài)合批、動(dòng)態(tài)合批。
對(duì)于建模合批:這里不建議在建模時(shí)就把各種物體的網(wǎng)格合并為一個(gè),因?yàn)檫@樣做會(huì)產(chǎn)生一個(gè)巨大的且有很多頂點(diǎn)數(shù)的物體,由于其體積過大,CPU無法在視錐體剔除階段將其剔除,那么就會(huì)有過多的網(wǎng)格信息需要從CPU傳到GPU,此時(shí)容易引發(fā)帶寬問題。這里推薦的做法是建模時(shí)要思考個(gè)體與整體的可見性問題,例如:對(duì)于書架里的書,當(dāng)里面一本書可見時(shí),通常一組書都可見,那么就可以把一組書的網(wǎng)格合并為一個(gè),甚至當(dāng)一本書可見時(shí)通常整個(gè)書架都可見,所以書和書架的網(wǎng)格都可以在建模時(shí)就合并在一起。
對(duì)于靜態(tài)合批:對(duì)于靜態(tài)不會(huì)動(dòng)的物體,在Unity中我們可以將其標(biāo)記為 Static,即靜態(tài)物體,那么在游戲運(yùn)行時(shí),Unity就會(huì)對(duì)使用相同材質(zhì)的靜態(tài)物體的網(wǎng)格進(jìn)行合并。注意:Unity并不是簡(jiǎn)單的把所有靜態(tài)物體都合并為一個(gè)網(wǎng)格。 Unity在靜態(tài)合批時(shí)也會(huì)考慮到視錐體剔除問題,參與靜態(tài)合批的物體自身會(huì)有一個(gè)索引,反過來根據(jù)索引我們也可以找到具體的物體對(duì)象,那么就可以動(dòng)態(tài)的決定該對(duì)象是否隱藏(參與渲染)。
對(duì)于動(dòng)態(tài)合批:一些頂點(diǎn)數(shù)較小的網(wǎng)格,在運(yùn)行時(shí)Unity會(huì)動(dòng)態(tài)的對(duì)它們的網(wǎng)格進(jìn)行合并,因此即使這些物體是動(dòng)態(tài)的也沒有關(guān)系。 注意:要使用靜態(tài)合批和動(dòng)態(tài)合批,首先要在設(shè)置中開啟:
在 PlayerSetting 中開啟靜合批和動(dòng)態(tài)合批
動(dòng)態(tài)合批的頂點(diǎn)數(shù)限制的具體定義:
一個(gè)網(wǎng)格, 如果 Shader 中使用了 Vertex Position, Normal 和 單個(gè)?UV,那么只有在頂點(diǎn)數(shù)不超過 300 個(gè)時(shí),該網(wǎng)格才能參與動(dòng)態(tài)合批。
一個(gè)網(wǎng)格,如果 Shader 中 使用了 Vertex Position, Normal, UV0, UV1和Tangent,那么只有在頂點(diǎn)數(shù)不超過 180 個(gè)時(shí),該網(wǎng)格才能參與動(dòng)態(tài)合批。
還有一些情況會(huì)導(dǎo)致無法進(jìn)行動(dòng)態(tài)合批,如 Shader 有多個(gè)Pass、材質(zhì)不同的物體無法合批在一起等等,github.com/Unity-Technologies/BatchBreakingCause 這篇官方文檔介紹了所有導(dǎo)致合批失敗的原因。通過 Unity的 FrameDebuger 我們也可以知道一些合批失敗的原因:
對(duì)于原本能合批卻合批失敗的物體,F(xiàn)rameDebuger會(huì)給出原因
動(dòng)態(tài)合批通常的應(yīng)用場(chǎng)景有兩個(gè):粒子系統(tǒng) 和 UI。 粒子系統(tǒng)沒什么好說的,要注意的是 UI 的動(dòng)態(tài)合批:
1、不同 Canvas 下的 UI 無法動(dòng)態(tài)合批
2、不同層級(jí)下的UI無法動(dòng)態(tài)合批(這里的層級(jí)可以理解為 一個(gè) UI 其下面墊了幾層的UI,這一塊可以根據(jù) FrameDebuger 的合批結(jié)果來進(jìn)行調(diào)整)
3、alpha = 0,depth = -1 的 UI 無法進(jìn)行合批
4、depth = x 的 UI,只能與 depth ≤?x 的 UI 進(jìn)行合批
5、動(dòng)態(tài)合批本身也是有代價(jià)的。 當(dāng)調(diào)用 Canvas.BuildBatch 或者,UI 元素產(chǎn)生變化時(shí) 就會(huì)重新進(jìn)行 動(dòng)態(tài)合批。 因此我們應(yīng)該盡量把經(jīng)常產(chǎn)生變化的 UI 和 不怎么產(chǎn)生變化的靜態(tài)UI 分別放在兩個(gè) Canvas 下,這樣可以降低 UI 動(dòng)態(tài)合批的復(fù)雜度,加快合批運(yùn)行的時(shí)間。
過多的材質(zhì)。過多的材質(zhì)與上面的合批基本原因和處理方法是一樣的。場(chǎng)景中有大量的材質(zhì)就會(huì)導(dǎo)致收集材質(zhì)信息時(shí)變得復(fù)雜。那么對(duì)于能夠合批的物體,合批后它們使用的就是同一個(gè)材質(zhì)。 而對(duì)于材質(zhì)使用的Shader和屬性都一樣,但貼圖不一樣的情況,我們可以試著將貼圖進(jìn)行合并.
復(fù)雜的動(dòng)畫。Unity是可以對(duì)動(dòng)畫文件進(jìn)行壓縮的,對(duì)于一些動(dòng)畫我們不需要太高的精度那么就可以進(jìn)行壓縮,來加快動(dòng)畫運(yùn)算的速度和減少動(dòng)畫文件的體積。并且對(duì)于動(dòng)畫文件中存儲(chǔ)的一些數(shù)值的精度我們可以通過代碼工具來修改,從而進(jìn)行動(dòng)畫壓縮,例如對(duì)于動(dòng)畫文件里 1.123456789 的數(shù)值,我們可以修改為 1.1234。并且有的動(dòng)畫文件中,某一幀和它的前一幀和后一幀是沒有變化的,那么該幀就可以刪除,這個(gè)也可以用代碼工具來檢測(cè)和解決。
2、?CPU向GPU傳遞渲染所需數(shù)據(jù) 階段的性能點(diǎn)
這一部分就是帶寬問題了。除了壓縮資源就是壓縮資源。當(dāng)然還有一些不在本篇范疇內(nèi)的手段(流式加載、VirtualTexture 等)
貼圖資源。
壓縮格式:PC端壓縮格式一般保持默認(rèn)就好了。移動(dòng)端貼圖建議使用ASTC6x6進(jìn)行壓縮,如果覺得壓縮后效果不行 可以換為 ASTC4x4。對(duì)于 HDR 貼圖 可以采樣 ASTC HDR 6x6 或 4x4 的壓縮格式。?一些用來保存高精度數(shù)據(jù)的貼圖,如果沒有 Alpha 通道,可以使用 RGB9e5 32bit Shared Exponent Float 的壓縮格式,有 Alpha 通道的話那就只能用 RGBAHalf 的格式了。
貼圖大小:貼圖大小就是盡可能的小,比如先做一個(gè) 2048x2048 的貼圖,然后不斷降低尺寸,直到效果上發(fā)生明顯變化并且無法接受時(shí),就得到了最適合的貼圖大小。并且對(duì)于一些具有四方連續(xù)特性的細(xì)節(jié)貼圖,我們可以只取其中的一小部分,放在一個(gè)尺寸很小的貼圖中,然后通過 Tilling And Offset 來采樣得到完整的內(nèi)容。
Mipmap:對(duì)于始終只出現(xiàn)在很遠(yuǎn)處的物體,其貼圖尺寸我們可以給很小,并且不需要生成 Mipmap。 同樣對(duì)于始終只出現(xiàn)在很近處的物體,我們可以給個(gè)較大的貼圖尺寸,并且不需要生成 Mipmap
網(wǎng)格資源
建模時(shí)控制好網(wǎng)格的頂點(diǎn)數(shù)
網(wǎng)格 LOD 策略
曲面細(xì)分著色器配合高度圖和區(qū)塊劃分來做到地形LOD
在網(wǎng)格資源設(shè)置中,關(guān)閉不需要導(dǎo)入的東西
可以嘗試開啟 Mesh Compression 對(duì)網(wǎng)格進(jìn)行壓縮,看得到的效果是否可以接受
非靜態(tài)物體(不需要參與烘焙光照貼圖的物體)取消勾選 GenerateLightmapUV
對(duì)于一些頂點(diǎn)數(shù)很多,同時(shí)體積很大很容易一直出現(xiàn)在視野范圍內(nèi)的物體。可以將其拆分為多個(gè)物體。這樣在經(jīng)過視錐體剔除后,就不需要向GPU傳入太多的頂點(diǎn)數(shù)據(jù)。
3、CPU發(fā)起DrawCall 階段的性能點(diǎn)
DrawCall 的數(shù)量,要減小 DrawCall 的數(shù)量,其實(shí)就是減少物體和材質(zhì)的數(shù)量,這些在上面的 CPU計(jì)算和收集渲染所需數(shù)據(jù)組裝描述符和材質(zhì)階段 部分已經(jīng)介紹了,這里就不贅述了。另外對(duì)于 DrawCall 數(shù)量的影響就是 Shader 的 Pass 數(shù),和在前向渲染的管線中點(diǎn)光源的數(shù)量,但這兩方面屬于是那種如果在不降低渲染效果的情況下那么就該是多少就是多少?zèng)]辦法減小的問題,如果非要減小那么就要對(duì)渲染管線進(jìn)行改造,插入一些現(xiàn)代的優(yōu)化方案,由于這些方案通常都比較復(fù)雜,不在本篇范疇類,有機(jī)會(huì)會(huì)專門做一篇進(jìn)行介紹。
DrawCall 的復(fù)雜度。DrawCall 不單單只是一個(gè)指令,一個(gè) DrawCall 中通常包含了多個(gè)指令。那么其包含的指令數(shù)的多少也就決定了其本身的復(fù)雜度。而 DrawCall 中最常見的指令就是告訴GPU材質(zhì)的屬性:
一個(gè)DrawCall中包含了多條指令
對(duì)于設(shè)置一個(gè)浮點(diǎn)數(shù)的值,需要調(diào)用 一次 glUniformf 指令(這里以 OpenGL 為例),而設(shè)置一個(gè)浮點(diǎn)向量的值,同樣也只需要調(diào)用 一次 glUniform4fv 指令。因此我們就可以把 4個(gè)浮點(diǎn)數(shù)變量 合并為 1個(gè)浮點(diǎn)向量變量。這樣 4條指令就可以合并為 1條指令。而要做到這樣,我們只需要在編寫Shader時(shí),將4個(gè)浮點(diǎn)變量的聲明改為1個(gè)浮點(diǎn)向量的聲明。但是浮點(diǎn)向量在材質(zhì)面板上的顯示方式對(duì)于美術(shù)極不友好,調(diào)節(jié)起來很變扭也很不直觀,這里可以使用 ShaderGUI 和 MaterialPropertyDrawer 類來對(duì)材質(zhì)面板進(jìn)行定制,以達(dá)到美術(shù)友好,這一部分我會(huì)放在后面的 騷操作篇進(jìn)行細(xì)說。
4、GPU進(jìn)行渲染和計(jì)算 階段的性能點(diǎn)
Overdraw
對(duì)于特效引起的Overdraw,一定要在特效制作時(shí)關(guān)注 "像素填充比" 這一概念:
像素填充比,即在一個(gè)特效面片中 alpha不為0 的像素 占整個(gè)面片像素的比例。在特效制作時(shí),要讓像素填充比盡可能的大。下面給出例子:
對(duì)于上面這個(gè)特效面片,像素填充比就過小,會(huì)產(chǎn)生大量沒有必要的 Overdraw。此時(shí)應(yīng)該改變網(wǎng)格,減低片面網(wǎng)格的高度,或者將網(wǎng)格做成月牙形。
而對(duì)于由于渲染順序不合理導(dǎo)致的 Overdraw,就只有自己根據(jù)實(shí)際情況去調(diào)整渲染隊(duì)列了。例如對(duì)于一個(gè) 有河流、草、樹木 和 地面的場(chǎng)景,我們應(yīng)該 先渲染草,然后渲染樹木的樹葉、之后是樹木、接著是地面,最后是河流。這樣在渲染地面時(shí),地面就會(huì)有大量像素因?yàn)楸徊莺蜆淠菊趽醵鴽]能通過深度測(cè)試,也就沒有被光柵化渲染,從而減少了Overdraw。而因?yàn)楹恿魇且粋€(gè)半透明物體(我們可以透過河流看到河水下面的地面),因此河流需要在地面渲染之后才渲染,這樣才能和地面做半透明混合。
Shader本身的計(jì)算復(fù)雜度
LUT策略。我們可以把Shader中這類計(jì)算:其輸入變量的值范圍在0~1或者可以轉(zhuǎn)換為0~1,其輸出變量的值范圍也在0~1或者可以轉(zhuǎn)換為0~1,那么我們就可以把0~1范圍的所有輸入都看作一個(gè)貼圖的UV值,而0~1的所有輸出都看作一個(gè)貼圖的顏色值。那么我們就可以提前離線的將所有輸入值都進(jìn)行計(jì)算得到輸出值,并存儲(chǔ)在一個(gè)貼圖中。那么 Shader 在 實(shí)時(shí)計(jì)算時(shí),我們只需要拿到輸入變量去采樣貼圖即可,從而避免了復(fù)雜的運(yùn)算。
合并運(yùn)算。對(duì)于 GPU 來說,一個(gè)浮點(diǎn)數(shù)的運(yùn)算 和 一個(gè)浮點(diǎn)向量的運(yùn)算使用的指令數(shù)是相同的。因此如果幾個(gè)浮點(diǎn)數(shù)需要做相同的運(yùn)算,那么就可以先把這幾個(gè)浮點(diǎn)數(shù)合并為幾個(gè)浮點(diǎn)向量,然后再運(yùn)算。并且,Unity在編譯著色器時(shí),會(huì)在一些地方幫我們做出這種優(yōu)化。
在移動(dòng)端還要注意數(shù)值變量的精度問題。在移動(dòng)端GPU上,數(shù)值變量有 fixed、half、float 的區(qū)分,其數(shù)值精度分別為 8位、16位和32位,位數(shù)越高,計(jì)算過程就越慢。但在一些GPU上(如蘋果的GPU 和 部分華為手機(jī)使用的GPU),如果運(yùn)算結(jié)果超過了其定義的精度范圍,就會(huì)出現(xiàn)渲染異常的情況(渲染結(jié)果出現(xiàn)黑色塊,或渲染結(jié)果不正確等)。這里我的建議是,在聲明變量時(shí),顏色使用fixed,世界空間下的位置信息使用float,如果采樣的貼圖會(huì)用到很大的Tilling那么uv也是用float,否則使用 half,其它的變量都使用half。
在移動(dòng)端還要注意GMEM_Load機(jī)制問題。GMEM Load 即 Graphic?Memory Load 在 移動(dòng)端 GPU 的 TileBase 架構(gòu)下, 其觸發(fā)表明上一幀的 Frame Buffer 在這一幀渲染時(shí)被從 GPU 主存加載到了正在渲染的 Tile 內(nèi)存中。其會(huì)引起嚴(yán)重的渲染性能問題!例如在一款手機(jī)上屏幕被分為了30個(gè)Tile, 如果觸發(fā)了 GMEM Load 那么在每次渲染一個(gè) Tile 之前都會(huì)從 主存 加載 FrameBuffer,而 Frame Buffer占用內(nèi)存比較大,加載時(shí)間會(huì)比較慢,并且在加載完畢后 GPU 內(nèi)部還需要一系列調(diào)度才能讓渲染開始進(jìn)行,因此 GMEM Load 會(huì)很大程度的降低GPU運(yùn)行的效率。 在 Unity 中觸發(fā) GMEM Load 的操作有:
開啟 HDR。在移動(dòng)端即使開啟了 HDR,顏色緩沖仍然是 RGBA8 格式的,Unity 會(huì)創(chuàng)建出一個(gè) RT(RGBA Half 或者 R11G11B10 格式,取決于你的 Graphic Setting),渲染結(jié)果先輸出到這個(gè) RT 上,并做了編碼,之后該 RT 通過解碼和Tonemapping 輸出到顏色緩沖上(相當(dāng)于一個(gè)后處理),但是 Unity 自己的這個(gè)后處理會(huì)觸發(fā) GMEM Load
GrabPass。因?yàn)?GrabPass 就是直接復(fù)制 Frame Buffer 的顏色緩沖。
相機(jī)的 ClearFlag 是 DepthOnly 或者 DontClear (這個(gè)在我的記憶中不是那么確定,總之好像確實(shí)有可能會(huì)導(dǎo)致GMEM Load)
混亂和大量的RT (這里說的混亂和大量是因?yàn)橥ǔ:?jiǎn)單的使用 RT 是不會(huì)導(dǎo)致 GMEM Load 的,但某些情況下會(huì)觸發(fā),具體是哪些情況我也說不上來,因?yàn)橛|發(fā)時(shí) RT 的管理真的太亂了...)。 另外對(duì)于不需要 深度/模板緩沖的RT,創(chuàng)建時(shí)要把它的 depth 設(shè)為 0,這樣即使因?yàn)?RT 觸發(fā)了 GMEM Load,由于 GMEM Load Color 和 GMEM Load Depth and Stencil 是兩個(gè)分開的操作,所以這樣可以避免 GMEM Load Depth and Stencil,從而做到盡量避免 GMEM Load。
?5、在一些情況下GPU會(huì)向CPU回傳數(shù)據(jù) 階段的性能點(diǎn)
沒什么好說的,這里能引起的只有帶寬問題。通常游戲開發(fā)中不會(huì)遇到這一階段。對(duì)于這一階段我們只需要留意使用 GPU 做計(jì)算功能時(shí),其結(jié)果應(yīng)該盡可能的是留在GPU的內(nèi)存中,作為GPU后面計(jì)算或渲染的輸入,如果一定要把結(jié)果傳給CPU,那就要注意結(jié)果的大小,避免產(chǎn)生帶寬問題。