移動遊戲性能優化

1. 前言
html

  不少年前就想將這些年工做中積累的優化經驗撰寫成文章,但懶癌纏身,遲遲未動手,近期總算潛下心寫成文章。程序員

  涉及到具體優化技巧時,我會盡可能闡述原理和依據,讓讀者知其然也知其因此然。算法

  要徹底讀懂這篇文章,要求讀者有必定的計算機語言/圖形學/遊戲引擎基礎。但願讀者看完後能將本身的遊戲性能優化到必定的高度,使得遊戲的效果和效率在高/中/低端設備都能符合或超出產品的預期。編程

  爲了方便描述,下面不少地方我會以以前在西山居作的一款RTS移動遊戲項目(簡稱遊戲Z)舉例,以Unity爲圖例,可觸類旁通用到其餘商業或自研引擎中。windows

1.1 性能優化的重要性數組

  想必玩過遊戲的人會常常感嘆:遊戲怎麼那麼卡?手機怎麼那麼燙?耗電怎麼那麼快?緩存

  其實這些問題都歸結於性能問題,性能優化對遊戲的重要性不言而喻。性能優化

  對新遊戲項目,中前期可能一直在趕需求趕效果,忽略了性能優化的工做,到中後期性能瓶頸必然會凸顯。若是前期作到有效組織資源,制定美術規範,完善開發流程和工具鏈,後期優化起來會順手不少,少走不少彎路。網絡

1.2 優化Tips數據結構

  不少年前,我剛大學畢業,就聽到一位圖形界的大神說過:優化無止境,優化的最高境界是不渲染。

  之前不能徹底明白他的話,以爲有點誇張,但我作過幾個遊戲項目的性能優化工做以後,深有體會,這麼多年過去,言猶在耳,應驗了真香定律!

  優化的本質就是不渲染少渲染用更省的方法渲染

1.2.1 分清主次

  優化性能首先要找出性能瓶頸,對性能影響最大的地方先優化,接着對次影響的進行優化,以此類推。

  若是能遵照這條規則,優化效果和花費時間的曲線關係大體以下圖:

  

  即前期的優化效果會很是明顯,但隨着時間的推移,花的時間愈來愈多,優化的效果反而逐漸放緩。這告訴咱們:

  1. 首先找出性能瓶頸,優化效果最明顯;

  2. 優化無止境,後期優化效果和時間比下降,要適可而止。

1.2.2 善用分析工具

  工欲善其事必先利其器。善用分析工具能夠快速定位出性能瓶頸,達到事半功倍的效果。

  性能分析工具請參看:1.3 輔助工具

1.2.3 平衡好性能和效果

  每一個遊戲偏重點不同,有些遊戲偏重效果而不太講究性能,有些遊戲偏重效率而犧牲效果,有些遊戲二者需兼顧。

  從個人經驗而言,大多數遊戲開發者或者玩家,更偏重性能。舉個例子,大多數人在玩吃雞遊戲時,爲了幀率穩定和耗電慢一點,將全部的畫質參數開至最低。

  若爲性能故,效果皆可拋。

1.2.4 定製優化參考指標

  每一個項目在優化前,須要定製一個具體的指標參數,好比高中低端機跑在什麼設備上,要達到什麼樣的幀數等等。

  遊戲Z定的參考指標以下表:

  

  此標準是2017年上半年定的,如今能夠酌情放寬參數。

  定好標準後,就以此爲依據進行優化,參數指標達到後便可認爲優化任務完成。

1.2.5 作好效果等級管理

  建議將等級管理邏輯抽象成單獨的模塊,增長QualityManager的角色,負責統籌管理和實現等級相關的邏輯。其它模塊要只要調用這模塊的接口,能夠輕易實現差別化邏輯。

  此外,QualityManager能夠收集遊戲關鍵參數:fps/網絡Ping值/流量,對外提供查詢接口。

1.2.6 具體狀況具體分析

   每一個遊戲項目的具體狀況不同,不可生搬硬套本文涉及的參數和方法,否正可能拔苗助長,花費了時間,性能沒能很好地提上去。

1.3 輔助工具

  目前市面上分析工具不少,特性和適用的平臺都不一樣,下面就簡單介紹經常使用的工具,具體使用方法另行搜索。

1.3.1 引擎分析工具

  1. Unity的Profiler

  

  Unity Profiler能夠查看CPU/GPU/內存/音頻/物理/網絡等模塊的具體消耗參數,是Unity遊戲必備的性能分析工具。

  2. Unreal

  Unreal 3及以前的版本要先用命令行生成profiler文件,再經過UnrealFrontend加載生成的文件查看消耗數。

  Unreal 4提供了相似Unity的Profiler的窗口,更加便捷。

  更多參看:Unreal Profiler Tool Reference

1.3.2 IDE分析工具

  1. Visual Studio的性能探查器

  

  VS只可運行在windows平臺,可對當前項目/指定的exe/運行的進程進行CPU和GPU性能採樣並查看具體的消耗指標。

  2. XCode的Instruments

  

  XCode只可運行在Mac OS,可調試Mac OS和iOS的APP的CPU/GPU/內存等性能參數。

  3. Android Studio的Profiler

  

  Android Studio可運行在Windows和Mac OS,但只可分析Android的APP,參數包括CPU/內存/網絡等。

1.3.3 GPU廠商工具

  幾乎每一個GPU大廠都提供了調試自家產品的GPU分析工具。它們與引擎和IDE的工具最大的不一樣點是:能夠查看每次Draw Call的渲染狀態/引用的資源/繪製的畫面,以及其它獨特參數。

  1. Tegra Graphics Debugger(NV)

  

  工具自己可運行在各個主流系統,也能夠分析OpenGL和Vulkan,但只支持NV旗下的Tegra K1和X1系列GPU。

  2. Mali Graphics Debugger(Arm)

 

  相似於Tegra Graphics Debugger, 但全面支持OpenGL的各類版本,還支持Vulkan和OpenCL的調試。

  3. PVRTrace(Imagination Technology)

  只可調試使用Imagination Technology公司GPU的App。

  4. Adreno Profiler(高通)

  只可調試使用高通旗下GPU的App。

  5. PerfHud(NV)

  只支持PC程序,很是強大的GPU調試工具,但沒法調試移動設備,須要依賴模擬器。

  6. PerfHud ES(NV)

  PerfHud移動版,支持移動設備調試, 須要下載整個CodeWorks for Android開發包。

1.3.4 其它第三方工具

  1. PIX(MS)

  只運行在windows平臺,只支持DirectX的分析。

 

2. 資源優化

  病從口入,資源比如是入口,它們若出現問題,會引起一連串性能問題。相反,資源如果優化得好,後面全部章節的性能均可受益。這也是把資源優化的章節提到最前的緣由。

2.1 紋理優化

  紋理優化的目的是讓它們佔用的內存儘可能的小,那麼紋理加載進內存後,大小計算公式以下:

    紋理內存大小(字節) = 紋理寬度 x 紋理高度 x 像素字節

    像素字節 = 像素通道數(R/G/B/A) x 通道大小(1字節/半字節)

  從上面公式能夠看到,紋理加載進內存後的大小跟尺寸/像素通道數/通道大小都有關係,咱們就從它們着手優化,還能夠經過提升複用率和合成圖集達到優化的目的。

2.1.1 紋理尺寸

  美術常犯的一個錯誤是無論什麼角色什麼場景,都會給模型貼上很大尺寸的貼圖,一般大於1024x1024。

  

  好比遊戲Z是斜45度固定視角對戰遊戲,在畫面中角色渲染所佔的畫面很小(約150x150),但美術在最初給全部角色模型都製做了1024x1024的貼圖。

  根據項目實際狀況,我將全部角色貼圖都縮小至256x256,角色貼圖佔用縮小至1/16。

  除了角色貼圖,武器/裝備/特效/場景等等全部涉及的貼圖都縮小至合適的大小。這裏的合適大小是指渲染對象在畫面中大多數狀況下不可能達到的最大尺寸,這個尺寸最好保持2的N次方。

2.1.2 紋理通道

  通道優化的目的是下降像素所佔的大小,能夠經過如下方法達到目的:

  1. 去除Alpha通道。能夠減小通道數量,適用於不須要Alpha混合或Alpha Test的角色和物件。

  2. 應用單通道圖。也能夠減小通道數量,好比灰度圖,地形高度圖,掩碼圖,Shader掩碼圖等等。

  3. 使用16位代替32位圖。例如RGB444/RGBA4444就能夠減小像素通道大小。

  4. 壓縮貼圖適應不一樣的平臺。例如:

    Windows能夠壓縮成DDS,其中DDS細分DX1~DX5共5種格式,每種應用場景略有不一樣,但它們只能用於DirectX。

    Android能夠壓縮成ETC1(不帶Alpha)或ETC2(可帶Alpha)。

    iOS能夠壓縮成PVRTC格式。

  它們都是GPU直接支持的紋理格式,能夠顯著減小內存/顯存/帶寬的佔用。

  5. 避免使用JPG/高壓縮率的PNG/GIF等低質量格式。由於當前主流商業引擎在遊戲發佈過程當中,會自動壓縮全部紋理,而保留原畫質的紋理能夠減小紋理壓縮後的畫質損失。

2.1.3 提升紋理複用率

  如下方法提升貼圖複用率:

  1. 創建共享圖庫。將通用的元素放至共享庫,例如按鈕/進度條/背景/UI通用元素等。

  2. 用九宮格圖代替大塊背景圖。九宮格在遊戲開發中是比較常見的UI組件。

  3. 紋理元素經過變換可組合成複合紋理。例下圖,上下左右對稱的背景圖能夠用4張相同貼圖實例經過旋轉/翻轉後得到。

  

  4. 九宮格+UI元素能夠組合成很複雜但消耗相對較小的UI界面。

2.1.4 紋理圖集

  圖集就是一堆小尺寸紋理元素合成的紋理貼圖(以下圖)。

  

  圖集能夠下降IO加載次數,也能夠減小Draw Calls(詳見4.2 Batch合批)。

  但也有反作用,一是可能超出設備支持的最大尺寸,二是可能出現大片空白像素(以下圖)。

  

  對於反作用一,能夠限制圖集的最大尺寸(一般不要超過2048x2048),分拆成多張圖集。

  對於反作用二,能夠針對性地調整紋理元素的佈局或尺寸,使得合成的圖集儘量佔滿有效像素。

  適合生成圖集的資源有:UI界面,道具圖標,角色頭像,技能圖標,序列幀,特效等等。

  若是是Unity引擎,能夠用SpritePacker很方便地生成和預覽圖集。

  若是是自研引擎,能夠用TexturePacker的命令行工具合成。

2.2 UI

2.2.1 UI圖集

  全部UI元素都生成圖集,並且確保每一個界面生成單獨的圖集,這樣能夠在界面銷燬時能夠及時釋放UI紋理。

  要儘可能確保每一個界面只引用到本身的圖集和共享庫圖集,避免引用到其它界面的圖集。

  

  上圖所示,界面A引用界面A圖集和共享圖集是容許的,但儘可能不要引用界面B等其它圖集。

  但實際在遊戲開發過程當中,很難保證美術作到這一點,一般存在如下問題:

  1. 若是界面A確實要用到界面B圖集的某個元素,怎麼辦?

  參考解決方法:要看被引用元素的通用度,若是隻是界面A和B在用,能夠將被引用元素拷貝到界面A圖集下;若是其它界面也會引用到,就能夠將它移到共享圖庫。

  2. 有些UI紋理很大且不少界面都有用到,若是放在共享圖庫會致使共享圖庫急劇膨脹,怎麼辦?

  參考解決方法:大尺寸紋理建議用九宮格+細節圖,或經過組合的方式來代替。

  3. 如何保證美術製做的UI只引用到自身圖集和共享圖集?

  參考解決方法:實現批處理檢查工具,找出每一個UI界面引用到的圖集列表,引用的圖集超過2個即是不合格。

2.2.2 UI層次

  因爲UI元素不少,主流商業引擎都會對它們合批以減小Draw Calls,但合批優化是有條件的:

  1. 使用相同的材質。使用引擎默認UI材質+UI圖集能夠知足這個條件。

  2. 繪製順序是連續的。UI的繪製順序一般就是在場景中的節點順序。

  下圖有4張UI圖片,但它們都用了系統默認材質,都是共享圖集的元素,而且它們在場景中的順序也是相連的,因此知足合批優化的條件,最終SetPass Calls(Draw Calls)是1。

  

  然而,在實際製做UI過程當中,常常會破壞UI合批優化的條件:

  1. 同個界面每每會引用自身圖集和共享圖集,破壞優化條件1。

  2. 界面一般都帶有文本,而文本一般是引擎自動生成的另一張或若干張圖集,破壞優化條件1。

  3. UI節點層次混亂,不一樣圖集的元素相互交叉,破壞優化條件2。

  4. 部分UI元素使用了自定義材質或Shader,破壞優化條件1。

  分析了緣由,在UI製做時,就要儘可能避免這些狀況發生。

2.2.3 UI的其它優化

  1. 禁用MipMaps。MipMaps的原理是根據繪製對象在繪製空間的大小選取合適的紋理層級,它會增長30%的內存/顯存開銷。而UI一般都是等長等寬的,跟攝像機距離無關,因此要禁用UI的MipMaps。

  2. 保持UI紋理的原始尺寸。縮放一般會帶來額外的開銷,並且會使UI變模糊,下降畫質。

  3. 避免使用大尺寸的背景圖。大背景圖耗內存,一般還不能共用。可用九宮格+細節圖組合而成。

2.3 字體

  說字體是性能的殺手絕不爲過。

  遊戲使用的字體通常是ttf格式,單個ttf字庫少則5~10M,多則10~20M。

  在文本繪製前,引擎會將字庫加載進內存,佔用較大的內存空間;在文本繪製時,引擎會在內存中開闢若干張紋理圖集緩存字體紋理。

  每一個字至少要兩個三角形,如果有陰影/描邊/發光等效果,三角形數量擴大數倍之多(以下圖)。

   

  能夠經過如下建議優化字體的性能:

  1. 控制字體文件數量。除了系統默認字體,自定義字體控制在1~2個爲宜。

  2. 少用字體的陰影/描邊/發光等效果。

  3. 剔除字庫中無用的字形。能夠藉助FontSubsetGUI或FontPruner給字庫瘦身。

    

  字庫瘦身更多參看這裏

2.4 模型

  模型特別是帶有骨骼動畫的模型在性能消耗中佔據很是大的比重,它們會顯著增長CPU/GPU/內存/顯存的負擔。因此,模型的優化尤其重要。

  模型涉及的數據比較多,包含了頂點/索引/材質等,而頂點又可能包含pos/color/uv/normal/tagent/skin等數據,咱們能夠從這些數據着手優化。

  

  上圖的模型頂點包含了pos/color/uv/normal/skin等數據。

2.4.1 模型數據

  模型的頂點數據一般包含pos/uv,但color/normal/skin等數據視不一樣類型的物件區分對待。好比,對於靜態物體,能夠去除skin;若是無需頂點變色,則能夠去除color。

  模型內pos.xyz/uv.xy/color.rgba等等數據默認用32位浮點數存儲,它們的數據表示範圍遠遠超出大多數遊戲的應用場景,能夠將它們壓縮至16位浮點數。

  模型的索引數據存儲了三角形引用的頂點序號,默認用32位unsigned int存儲,但絕大多數模型不可能超出16位unsigned short的範圍,故用16位整型足矣。

  Unity引擎在Model的Import面板可設置優化參數(下圖)。

  

2.4.2 模型輔助工具

  美術製做出的模型一般是高精度模型,雖然效果好,但每每在中低端機不須要這麼高的精度,這時候就要藉助一些工具進行優化。

  下面主要介紹Unity的工具,其它引擎應該有相似的工具。

  MeshBaker:模型合併插件,能夠對多個模型合成一個模型,從而減小模型個數,下降Draw Calls。多用於靜態物體合併,好比場景和地面靜態物體。

  SimpleLOD:模型減面庫,能夠離線或運行時給模型進行減面優化,也能夠方便地作成批處理工具。

2.4.3 模型美術規範

  一個模型儘可能只用一個材質,材質使用的貼圖大小要合理,太大浪費內存,過小畫質會模糊。

  建模時,剔除模型內部等不可見的頂點和三角面,合併重疊或相鄰的頂點,減小模型的頂點數和麪數。爲防止美術製做的模型精度太高,有必要對模型的頂點和麪數作限制。

  若模型帶骨骼動畫,需對骨骼數量作限制,單個模型的骨骼數量最好限定70個之內,否正不少低端設備沒法支持GPU蒙皮。對模型進行分類,重要模型骨骼數能夠多一些,次重要或不重要的更少骨骼數。

  遊戲Z對各種模型的限制以下表:

  

2.5 場景

  地形如果不復雜(好比王者榮耀/LOL的戰鬥場景),儘可能不用Terrain,用簡易模型代替,地表細節可用一張紋理表示,地表紋理取合適大小,一般不超過1024x1024。

  地形網格和地面靜態物體去掉陰影,若是某些物件確實須要陰影,可讓美術在製做地表紋理時加上陰影。

  地形有不少不可見的地方,能夠刪減那裏的模型網格。

  地面一般有不少裝飾物和特效,要關注它們的面數等規格是否超出了限制。

  一個場景只用一個平行光,實時像素光不要超過一個。用Lighting Map代替實時光,Lighting Map紋理尺寸不宜太大。

  對場景的面數和物體數量作限制。使用合批工具離線將地表類似的靜態物體合併,減小場景複雜度。

  對場景使用畫質分級策略,好比低畫質下,用最低模的場景,隱藏場景特效等。

  地表若有導航網格,導航網格能夠採用更精簡的模型,複雜的邊緣能夠簡化成簡單的幾何多邊形。

  

  上圖展現的是遊戲Z的一個場景,雖然地表物體比較複雜,但地形網格(綠線所示)很簡單,用少許的三角面表示了複雜的地表構造。

2.6 粒子

  粒子特效也是性能的一個大殺手,主要體如今:

  1. 每幀計算量大。涉及發射器/效果器/曲線插值等,耗費CPU性能。

  2. 頻繁操做內存。粒子在生命週期裏,實例持續不斷地建立/刪除,即使有緩存機制下,依然避免不了內存的頻繁讀取和碎片化。

  3. 每幀更新數據到GPU。從Lock頂點Buffer到寫入數據到GPU,會引起線程等待,也加劇CPU到GPU帶寬的負擔。

  4. 增長大量Draw Calls。粒子特效一般五花八樣,使用不少材質,致使引擎沒法合批優化,大量增長繪製次數。

  5. 致使Overdraw(過繪製)。粒子通常會開啓Alpha Blend,在同屏粒子多的狀況下,會形成嚴重的Overdraw。

  

  上圖展現的是遊戲Z主界面的過繪製狀況,越白表明過繪製越嚴重,能夠看出用了粒子的地方,顏色趨向白色。

  即然粒子致使性能嚴重降低,首先得從資源上着手優化和規範。具體方式有:

  1. 優化粒子屬性。關閉陰影,關閉光照;若能夠去掉紋理的Alpha通道,並關閉Alpha Blend和Alpha Test;

  2. 禁用粒子的高級特效。如模型粒子/模型發射器/粒子碰撞體等。

  3. 用最少的粒子效果器。關閉沒必要要的粒子效果器,採用簡單的方式代替。

  4. 控制粒子的材質數量。一個特效一般包含了若干個粒子系統,它們儘可能使用內置材質,使用相同的材質實例。

  5. 控制粒子的尺寸和貼圖大小。能夠粒子的尺寸,能夠減緩過繪製,控制貼圖大小,能夠減小帶寬和提升渲染性能。

  6. 制定粒子特效美術規範。下面是遊戲Z的粒子規範。

粒子美術規範

 單個粒子的發射數量不超過50個。
 減小粒子的尺寸,面積越大就會消耗更多的性能。
 粒子貼圖必須是2的N次方,儘可能控制64x64之內,極少許128x128或256x256,最大不超過256x256。
 儘量去掉粒子貼圖的Alpha通道。
 儘可能不用Alpha Test。
 儘可能使用已有的材質,提升合併渲染的優化機率。
 材質優先用Mobile目錄下的材質。
 儘量不用模型作粒子,若是使用,要控制模型面數在100之內,最大粒子數在5之內。
 單個特效渲染數據限制:
   小型特效(如受擊特效、Buff特效)的面數和頂點數在80之內,貼圖在64*64之內,材質數2個之內。
   中型特效(如技能特效)的面數和頂點數在150之內,貼圖在128*128之內,材質數4個之內。
   大型特效(如全局特效、大火球)的面數和頂點數在300之內,貼圖在256*256之內,材質數6個之內。

2.7 材質

  材質若控制很差,會破壞引擎的合批優化,提升渲染消耗。因此在項目前期,就有必要對材質作管理和規範。

  1. 充分使用引擎內置材質。引擎內置材質能知足基本材質需求,並且一般作了優化,因此首選內置材質。若是是移動遊戲,通常引擎也有移動版的材質庫。

  2. 創建共享的自定義材質庫。若是引擎內置材質不知足項目需求,就須要經過添加自定義材質來解決。對於自定義材質,要妥善管理,對它們進行分類,按規律命名。這樣對材質共享和管理都大有裨益。

  3. 按期檢查新加入的材質是否與已有的重複。若是有類似的材質,則刪除重複的。這個工做能夠由主程或主美執行,也能夠經過工具協助檢查。

2.8 制定美術規範

  制定美術規範時,需與主美/主策/製做人協商,結合項目具體狀況,給出合理的美術規範參數,並撰寫成文檔。

  定好規範後,有必要定時檢查項目裏的全部美術規範是否符合規範,揪出不符合的資源,讓美術修改。

  檢查美術是否合規,能夠寫批處理工具,提升效率。

  特效/場景/角色資源若不能批量處理成高中低配版本,就建議美術爲各個畫質等級製做不一樣的資源。

 

3. CPU優化

  性能優化最主要的一部分工做是CPU,CPU性能優化好了,離目標就成功了一半。

3.1 緩存計算結果

  緩存計算是空間換時間的經典應用,它適用於那些耗費大量CPU計算而計算結果無需每幀變化的邏輯。

  實現僞代碼:

複製代碼
std::map<KeyType, ValueType> _cache;

ValueType Calculate(KeyType key)
{
    // 先嚐試從緩存中獲取結果,有就直接返回。
    if (_cache.count(key) > 0)
    {
        return _cache[key];
    }
    // 緩存不存在,執行真正的計算,並緩存計算結果。
    ValueType res = DoCalculation(key);
    _cache[key] = res;
    return res;
}
複製代碼

  適用場景舉例:

  1. 複雜數學計算。Sin/Cos/Pow/Sqrt等運算要花費必定計算量,若是是第一次計算,能夠將結果緩存起來,下次遇到相同的計算,直接從緩存中取值。

  2. 物理模擬結果。物體的物理模擬過程耗費大量計算,但有些物體模擬完以後就處於靜止狀態,能夠將它以前的模擬結果存下來,防止每幀更新計算。現代主流商業引擎都支持這種優化。

  3. 光照貼圖。光照貼圖是離線將場景的靜態光影計算並緩存成貼圖,渲染時只須要採樣光照貼圖的顏色,極大下降了光照計算複雜度。

  4. 搜索結果。例如場景節點搜索,場景節點通常採用樹形結構,若是查找的節點很深,將顯著增長遍歷次數,此時頗有必要將查找結果緩存起來。

  5. 邏輯模塊複雜的計算。遊戲的邏輯模塊,涉及到複雜的計算均可嘗試用緩存法下降CPU負擔。

3.2 預處理

  緩存法利用空間換時間的思想,會增長內存開銷;而預處理是將時間轉移的思想,它並不會增長內存消耗。

  將須要花費大量時間加載或運算的邏輯,在啓動程序後/加載場景時/切換界面前/進入戰鬥前等時機預先計算或加載,避免渲染時因CPU負載太高出現幀率波動或卡頓現象。

3.3 限幀法

  限幀法簡單粗暴,但效果顯著,是常見的一種優化手段。

  限制頻率的對象能夠是World.Update,物理模擬,粒子計算,角色AI處理,角色狀態更新等等。

  限幀能夠經過如下方法實現:

  1. 計數法。用一個變量記錄更新次數,每累計到某個數才執行更新。

複製代碼
int _frameCounter = 0
void Update(float time)
{
    _frameCounter += 1;
  // 更新頻率降爲每10幀一次。
  if (_frameCounter % 10 == 0) 
  {
    DoUpdate();      // 執行真正的更新
    _frameCounter = 0;  // 重置計數器
  }
}
複製代碼

  2. 計時器。利用Timer機制觸發,每隔固定時間觸發一次更新。

  3. 協程。協程是運行於主線程的僞線程,但能夠模擬異步操做,沒有多線程的反作用。故而也能夠用於限幀操做。

  4. 事件觸發。每幀查詢狀態改爲事件觸發,也是遊戲經常使用的一種優化手段,用來限幀也很是有效。

3.4 主次法

  主次法跟LOD技法有殊途同歸之妙。思路也是將物件按重要程度劃分爲高中低級別,而後不一樣級別採用不一樣複雜度的效果或計算。

  這種思路在遊戲中能夠普遍應用,基本全部消耗高的邏輯或模塊均可以採用這個技法。例如:

  1. 畫質等級。將遊戲分爲若干等級,畫質最高到最低採用不一樣的渲染技術或資源,區別對待。

  2. 資源等級。場景/特效/燈光/物理效果/導航等等模塊均可以根據畫質等級或物件等級對應不一樣級別的資源,總體上能夠減小消耗,又能兼顧畫質效果。

  3. 角色分級。主角英雄/Boss等和小怪物/NPC區分開來,前者更新頻率更高,AI行爲更復雜更智能,然後者採用簡單效果或降頻更新。

3.5 多線程

  

  上面這張搞笑動圖相信不少人都看過,它形象生動地代表了當今設備多核常態化但主核承擔大部分工做忙到「吐血」而其它核在打醬油的囧態。

  在遊戲開發中,咱們能夠將一些邏輯經過建立線程的方式獨立出去,交給其它CPU核心處理,以緩解上面提到的現象。

  可獨立成線程處理的模塊:

  1. 文件IO。創建一個IO線程,定時檢查文件隊列是否有數據,如有便啓動IO加載,直至文件隊列爲空,又回到空閒輪詢狀態。主線程就不會覺得文件IO而處於等待狀態。

  2. 骨骼動畫。若是同屏動畫角色多,將佔據大量CPU計算。可建立一個或多個線程處理骨骼動畫計算,加速渲染流程。但隨之而來的是同步問題。

  3. 粒子計算。粒子的多線程跟骨骼動畫相似,也存在同步問題。

  4. 渲染線程。將渲染抽離出一個線程,主要是解決CPU與GPU相互等待的問題。常見的一種作法是創建一個渲染線程,按期去查詢渲染隊列是否有數據,若是有就提交至GPU進行繪製。

  5. 音視頻編解碼。若是遊戲有涉及音頻頻播放,而它們又佔據了較大的消耗,那麼開闢獨立的線程處理音視頻編解碼是有必要的。

  6. 加密解密。加密解密涉及的算法一般較複雜,佔用較多CPU性能,並且經常伴隨着文件IO或網絡IO,因此此時很是有必要將它們交給獨立的線程處理。

  7. 網絡IO。目前大多數遊戲都會開闢一個線程專門收發網絡數據,以免網絡處理影響主線程。

  值得注意的是,線程切換會帶來額外開銷,同步和死鎖問題也會提升邏輯複雜度和調試難度,是懸在程序員頭上的一把大刀。

3.6 引擎模塊

  引擎內部最耗CPU性能的模塊一般有:骨骼動畫/粒子計算/物理模擬/導航網格/相機裁剪和渲染等等。

3.6.1 動畫

  下降動畫採用頻率。

  減小關鍵幀數據。

  緩存動畫的插值結果。

  用簡單曲線插值(線性插值)代替複雜插值(貝塞爾曲線)。

  判斷動畫所附對象的可見性,如不可見,則不更新動畫。

  控制同屏動畫的個數。

  不一樣畫質加載不一樣級別的動畫資源。

3.6.2 物理

  靜態物理只用靜態碰撞體。

  下降物理模擬頻率。

  禁用複雜的碰撞體(如模型碰撞體),取而代之的是用若干個簡單幾何碰撞體組合。

  若物體不可見,則關閉物理模擬。

  控制同屏物理物體個數。

  對物體的重要程度作分級,重要性低的角色採用簡單物理效果或者刪除物理效果。

  Raycast射線檢查雖好用,但性能消耗也高,要儘可能下降視頻頻率,防止重複調用。

3.6.3 粒子

  粒子資源優化:詳見2.6。

  部分粒子特效考慮轉成序列幀渲染。

  考慮是否可用緩存/預加載/預計算的優化方式。

  對特效的重要程度分級,不重要的粒子不計算或者降頻。

  若GPU的負擔比CPU小,能夠將粒子更新計算移至GPU端。

  粒子物體可見性判斷,不可見則不計算和渲染。

3.6.4 導航

  簡化導航網格,用最少的面數表達複雜地面的導航構造,好比用平面代替地面凹凸不平的路面。(詳見2.5)

  尋路計算複雜,邏輯上須控制調用頻率,避免扎堆尋路。

3.7 邏輯優化

  邏輯的消耗也是CPU負擔高的罪魁禍首,主要體如今AI/算法/腳本等等模塊。

3.7.1 AI優化

  爲了簡化玩家操做和讓怪物更加擬人化,目前大多數遊戲都加入了AI功能,而AI行爲樹(下圖)是實現AI的關鍵。

  

  可是正常狀況下,AI行爲樹要每幀更新,每次需從根節點搜尋,一直找到合適的節點才更新,最壞的狀況會遍歷整棵樹。

  下面有幾種方法優化AI行爲樹:

  1. 緩存當前Action節點路徑。即將當前正在更新的節點和其父親節點都緩存起來,下次要更新時優先這個路徑搜尋,避免遍歷整顆樹。若是當前Action節點是持續性的,則這段時間內無需搜索節點,直接更新該節點便可。

  2. 下降AI的更新頻率。即強制下降AI頻率達到減負的目的,另外還能夠嘗試主次法/攤幀法/動態調幀法優化AI的更新。

3.7.2 算法優化

  思路是找出最耗CPU的算法或邏輯,優化之。

  1. 空間換時間。利用預排序/預處理/緩存/動態規劃等等思路換取CPU的性能。

  2. 選取更快的算法。屬於數據結構和算法的範疇,思路是將O(n2)下降成O(n)或O(logn),具體能夠參看《算法導論》《遊戲編程算法與技巧》《遊戲核心算法編程內幕》等書籍。

3.7.3 腳本優化

  一般引擎底層是用C++等Native語言實現,而腳本用動態語言(Java/C#/Lua/Python)實現,它們中間隔着一層厚重的模擬器或封裝層。Unity引擎與C#之間的關係以下圖:

  

  Unity在打包遊戲時會經過Mono將C#代碼生成IL中間語言,若是是iOS平臺,還會經過IL2CPP生成C++代碼。簡單點說,Unity引擎核心和C#等腳本語言的交互要經過Mono厚重的中間層。由此產生了額外的開銷,致使腳本語言運行效率低下。

  能夠經過如下一些方法下降腳本的開銷:

  1. 刪除腳本內的空回調。即使腳本對象的回調函數爲空,但也會產生引擎核心與腳本層的開銷。

  2. 腳本對象若是引用其餘對象,能夠在初始化時緩存。

複製代碼
class Tester
{
    private Object _obj = null;

    void init()
    {
        _obj = FindObject("MyObjectName"); // 初始化時先找到物體。
    }

    void update()
    {
        if (_obj)
        {
            _obj.update();    // 幀內直接訪問。
        }
    }
}
複製代碼

  3. 幀更新/循環語句內避免產生堆的臨時對象。能夠將臨時對象移至循環語句外,或聲明成類的成員,在初始化時賦值。

  4. 利用可見性回調。在可見性回調內作禁止/恢復比較耗時的操做。

  5. 字符串接很容易引發臨時對象,需警戒。可採用更高效的拼接方式,如C#的StringBuilder。

3.7.4 條件測試

  條件測試主要用於耗時的調用優化,將每幀必然更新的操做,加入各類條件檢查,以減小耗時操做的機率。

複製代碼
void Update()
{
    DoCalculation();    // 耗時操做
}
// 改爲:
void Update()
{
    // 加入各類條件測試
    if (_dirty && _visible && _moved && _timeInterval>0.1)
    {
        DoCalculation();
    }
}
複製代碼

3.7.5 避免重複

  遊戲通常涉及的模塊衆多,角色狀態機複雜,觸發事件多且雜,每每會在同一幀內屢次調用同一個耗時API,引起額外的開銷。

  能夠經過條件測試,時間間隔,Log輸出,調用棧調試等方法解決這個問題。

 

4. 渲染優化

  渲染優化的目的是減小Draw Calls,減小渲染狀態切換開銷,下降顯存佔用,下降帶寬和GPU負擔。

  在講解渲染優化以前,先了解渲染性能消耗點。

  1. Draw Call數量。

  Draw Call有些引擎也稱爲SetPass Call。一個Draw Call就是遊戲調用OpenGL/D3D等圖形渲染的繪製API一次(如OpenGL的glDrawArray和glDrawElements)。

  一次Draw Call完整地跑完了整個渲染管線(下圖),期間要涉及的數據/狀態/計算不少,繪製前會先建立各類GPU數據,還可能每幀更新這些數據,數據更新又涉及到帶寬。

  

  因此,每幀Draw Call數量是衡量渲染性能的關鍵指標。

  2. 渲染狀態切換。

  每次Draw Call前會對圖形渲染層設置一系列的渲染狀態,如是否開啓深度測試/是否開啓Alpha Test/是否開啓Alpha Blend等等。這些狀態經過圖形渲染的驅動層最終應用到GPU中(下圖)。

  

  從上圖能夠看到,應用程序(遊戲)發送的渲染指令,會通過OpenGL/DirectX等圖形層和顯卡驅動層,最終才能應用到GPU硬件。

  因爲當代顯卡驅動作了不少工做:狀態管理/容錯處理/邏輯計算/顯存管理等等,屬於重度封裝,會消耗較多性能。

  因此,儘量減小狀態切換,是優化渲染性能的重要措施。

  3. 帶寬負載。

  此處的帶寬是指CPU通過主板總線傳輸數據到GPU的能力,單位一般是GB/s。固然GPU也可經過總線傳輸數據到CPU,但傳輸能力遠遠低於CPU到GPU。

  

  上圖所示,CPU和GPU經過PCI-e總線相連,它們之間的傳輸能力是有上限的,這個上限就是帶寬。若是繪製須要傳輸的數據大於帶寬(即帶寬負載太高),就會出現畫面卡頓/跳幀/撕裂/延遲/黑屏等等各類異常。

  4. 顯存佔用。

  顯存即顯卡的內存,是集成在GPU內部的專用內存。一般用於存儲頂點/索引/紋理/各類Buffer等數據。

  若是遊戲顯存佔用太高,便會出現顯存分配失敗,致使畫面異常甚至程序崩潰。

  5. GPU計算量。

  現代顯卡基本都支持可編程渲染管線,涉及Vertex Shader/Geometry Shader/Fragment Shader(下圖),還涉及光柵化/片元操做。因此,若是Shader過於複雜或者片元過多,會極大提升GPU計算量,下降渲染性能。

  

  瞭解渲染具體開銷後,就能夠用下面的方法着手優化。

4.1 合批(Batch)

  合批(Batch)是將若干個模型合成一個模型,從而能夠只調用一次Draw Call的優化手段。合批解決的是Draw Call數量問題。

  合批的條件是全部被合的全部模型都引用同一個材質,否正沒法正常合批。

4.1.1 離線合批(Offline Batch)

  離線合批就是在遊戲運行前,先用工具把相關資源作合批處理,以減輕引擎實時合批的負擔。

  適合離線合批的是靜態模型和場景物件。如場景地表裝飾面:石頭/磚塊等等。

  離線合批方式有:

  1. 美術利用專業建模工具合批。如3D Max/Maya等。

  2. 利用引擎插件或工具。如Unity的插件MeshBaker和DrawCallMinimizer,能夠將靜態物體進行合批。

  3. 自制離線合批工具。若是第三方插件沒法知足項目需求,就要程序專門實現離線合批工具。

4.1.2 實時合批(Runtime Batch)

  不一樣於離線合批,實時合批是遊戲引擎在遊戲運行期完成的。Unity引擎分爲靜態合批和動態合批。

  1. 靜態合批(Static Batch)

  符合靜態合批的條件有兩個:一是模型有Static標記(即物體是靜態的,不能有移動/動畫/物理等),二是引用同一個材質實例。

  爲了提升靜態合批的機率,儘量將場景物件設爲靜態,而且相似的物件引用相同的材質。

  2. 動態合批(Dynamic Batch)

  動態合批是針對能夠運動的模型,但有更苛刻的要求,例如Unity要求:

  • 模型少於300個頂點,少於900個頂點屬性。
  • 不能有鏡像Transform。
  • 使用同一材質實例(注意:是實例,相同的材質不一樣的實例,也是不行的)。
  • 使用相同的光照圖(Lightmaps)。
  • 不能用多Pass Shader。

4.1.3 合批反作用

  合批優化雖能下降Draw Calls,但也有反作用:

  1. 增長CPU消耗。須要消耗CPU計算將多個模型合成一個,還涉及材質排序和蒐集等操做。

  2. 增長內存。須要額外開闢內存存儲合成的模型。

  3. 合成的模型頂點數有限制。移動遊戲一般用16位索引,若合成的模型超出16位無符號整數的範圍,渲染會出現異常。

4.2 渲染狀態優化

4.2.1 狀態緩存

  在引擎側,能夠使用狀態緩存減小渲染管線的切換。僞代碼:

複製代碼
class RenderStateCache
{
public:
    void InitRenderStates();
    {
        for (RenderStateType t=RenderStateType.begin; t<RenderStateType.end; ++i)
        {
            _renderStateCache[t] = GetRenderStateFromDevice(t);
        }
    }

    void SetRenderState(RenderStateType state, RenderStateValue value)
    {
        // 若是要設置的狀態和當前緩存的同樣,則忽略。
        if (_renderStateCache.count(state) > 0 && _renderStateCache[state] == value)
        {
            return;
        }
        _renderStateCache[state] = value;
        SetRenderStateToDevice(state, value);
    }

private:
    std::map<RenderStateType, RenderStateValue> _renderStateCache;
};
複製代碼

4.2.2 渲染狀態建議

  1. 少用Alpha Blend。開啓Alpha Blend了通常會關閉深度測試,沒法利用深度測試剔除多餘片元,致使片元數量增長,形成過繪製。因此要儘可能少用。

  2. 禁用Alpha Test。現代部分移動端GPU採用了特殊的渲染優化方式,如PowerVR採用Tile Based Deferred Rendering方式(下圖右),而Alpha Test會破壞Early-Z優化技術,可用Alpha Blend代替。更多看這裏

  

  3. 開啓背面裁剪。背面裁剪能夠將背向攝像機的面片剔除,減小頂點和片元的數據量。

  4. 開啓MipMaps。開啓後,渲染時會自動根據畫面尺寸選擇合適大小的紋理,從而下降帶寬,也能夠下降鋸齒,提升畫質效果。但UI界面不能開啓,緣由見2.2.3。

  5. 關閉霧。只在固定管線適用。

  6. 少用抗鋸齒。圖形API內置的抗鋸齒一般會增長紋理採樣次數數倍之多(下圖),因此要慎用。

  

4.3 控制繪製順序

  控制模型繪製順序的目的是充分利用深度測試,減小片元后續操做。特別是Early-Z技術的引入,此法效果更明顯。

  繪製順序是:先繪製已作好排序的不透明物體,再繪製Alpha Tested物體,最後渲染透明物體。

  僞代碼:

複製代碼
void Render()
{
    // 1. 繪製不透明物體
    SortOpaqueObjectsInViewSpace(); // 對不透明物體進行排序,須在相機空間,離相機近的排在前面。
    DrawOpaqueObjects();            // 繪製不透明物體,離相機近的先繪製。

    // 2. 繪製Alpha測試物體
    SortAlphaTestedObjectsInViewSpace(); // 對Alpha測試物體進行排序,須在相機空間,離相機近的排在前面。
    DrawAlphaTestedObjects();          // 繪製Alpha測試物體,離相機近的先繪製。

    // 3. 繪製透明物體(注意:繪製順序跟不透明物體恰好相反)
    SortTransparentObjectsInViewSpace(); // 對不透明物體進行排序,須在相機空間,離相機遠的排在前面。
    DrawTransparentObjects();            // 繪製不透明物體,離相機遠的先繪製。
}
複製代碼

4.4 多線程渲染

  在單線程渲染架構中,CPU性能消耗太高會影響GPU的渲染幀率,反之,GPU渲染過慢也會讓CPU一直處於等待狀態。

  多線程渲染就是爲了解決CPU和GPU相互等待的問題。

  以Metal/Vulkan等架構出現爲界限,將它們分紅兩個階段。

4.4.1 軟件級多線程渲染

  早期的圖形API和硬件架構都不支持多線程渲染,此階段多線程渲染能作的優化比較受限,只能將渲染提交獨立成一個線程,使之不會卡邏輯線程。

  開源圖形渲染引擎OGRE的多線程渲染實現方式有兩種:

  1. Middle-level Multithread。

  每一個渲染物體都有兩份實例,主線程改變其中一份數據,在下一幀給渲染線程使用。(下圖)

  

  這種方式實現很複雜,要維護物體的兩份實例,也不容易在多核CPU作擴展,不能充分發揮多核CPU的優點。

  2. Low-level Multithread。

  

  這種實現方式是將渲染物體的頂點等數據拷貝一份,邏輯線程修改其中一份數據,下一幀給渲染線程使用。

  除了以上兩種方案外,能夠給邏輯線程的若干邏輯(如Update/粒子/動畫)開闢多個線程(下圖),並行計算,縮短總體處理時間。

  

4.4.2 硬件級多線程渲染

  近幾年,Metal/Vulkan圖形架構橫空出世,基於硬件級別的多線程渲染的時代終於到來。它們的特色:

  1. 輕量化的驅動層。

  OpenGL的API和驅動作了不少邏輯封裝,用狀態機的方式實現渲染(下圖左)。而Metal/Vulkan與之不一樣的是,在驅動層只作少許的工做,爲應用程序提供直接訪問GPU硬件的接口,屬於輕量級封裝(下圖右)。

   

  從API架構上看,Metal/Vulkan的性能已勝出一大籌。

  2. 支持硬件級的多線程渲染。

  Metal/Vulkan支持並行渲染指令,方便CPU各個線程各自提交渲染指令和數據。

  下圖展現的是其中一種渲染方式,由多個線程建立不一樣的繪製命令,再由單獨的線程管理渲染命令隊列,統一提交給GPU繪製。

  

  因爲圖形API已經支持多線程渲染指令提交,再結合上一節講到的若干方案,將如虎添翼,渲染性能也會發生質的提高。

  目前主流商業引擎已經支持Metal/Vulkan,Unity2018.3已經支持Metal/Vulkan:

  

  Unity在Rendering設置面板能夠開啓多線程渲染:

  

4.5 光照模型(Lighting/Illumination Model)

4.5.1 Flat Shading(平面着色)

  根據表面法向量計算光照,並應用到整個面片上。速度最快,效果最差,容易暴露物體的多邊形本質(下圖)。

  

4.5.2 Gouraud Shading(高洛德着色)

  根據頂點法向量計算光照,再用插值計算出整個面的光照。效果比Flat shading稍好,但高光部分有瑕疵,過渡不夠天然(下圖)。

  

  可結合Phong Shading作優化,高光弱時用Gouraud Shading,高光強時用Phong Shading,可平衡效果和效率。

4.5.3 Lambert Shading(蘭伯特着色)

  物體表面向各個方向等強度的反射光,這種等同地向各個方向散射的現象稱爲光的漫反射。

  Lambert定律:反射光線的強度與表面法線和光源方向之間的夾角成正比(下圖)。它是一種理想的漫反射模型,但着色效果比高洛德要平滑。

  

  計算公式:

4.5.4 Half Lambert Shading(半蘭伯特着色)

  Lambert着色有個缺陷,就是背面受光少,常常處理死黑狀態,與受光面反差太大(下圖左)。

  因而其中的一種改進方案誕生了,它就是Half Lambert Shading,它渲染的畫面明暗關係沒那麼強烈,過渡更加天然(下圖右)。

  

  計算公式:

  其中a和b是常數,一般都取0.5,並且a+b = 1.0。

4.5.5 Phong Shading(馮氏着色)

  Phong着色將光照分紅自發光(Emissive)/環境光(Ambient)/漫反射(Diffuse)/高光(Specular)四個部分,每一個部分獨自計算光照貢獻量。是當前普遍應用的一種光照模型。

  

  其中高光計算公式:

  Phong着色效果以下:

  

4.5.6 Blinn-Phong Shading

  因爲Phong模型要用到反射矢量r,而r計算比較耗時(下圖),故有了Blinn Phong。

  

  Blinn Phong是Phong的一個改進,作法是摒棄反射矢量r,引入l和v的中間矢量h,而後利用n和h的夾角進行計算。

  

  高光計算公式:

  它渲染出的高光範圍更大(下圖),真實感不如Phong着色,但勝在效率更高。

  

 4.5.7 光照模型的選擇

  從性能上作比較:Flat > Gouraud > Lambert > Half Lambert > Blinn-Phong > Phong。

  但畫質效果恰好相反,因此每一個遊戲需根據具體需求作選擇。也能夠採用分級策略,高中低畫質分別採用不一樣的光照模型。

4.6 渲染路徑(Rendering Path)

4.6.1 經典頂點光(Legacy Vertex Lit)

  嚴格來講,它也是前向渲染的一種,但有些引擎(如Unity)將它單獨抽離出來。因爲光照計算在頂點,因此效果和消耗跟4.5.2 Gouraud Shading相似,是早期GPU使用較多的一種渲染方式。

4.6.2 前向渲染(Forward Rendering)

  前向渲染是傳統的一種渲染方式,受到普遍的硬件支持。它渲染的思路就是按照渲染管線的流程一步步渲染,最終將顏色繪製到Render Target(下圖)。

  

  它的消耗跟物體數量和燈光數量有關,是O(Nobject * Nlight)的關係,對於燈光數量較多的場景,顯得力不從心。

  光照計算僞代碼:

複製代碼
Color color = Color.black;
for each(light in lights)
{
    for each(object in objectsEffectedByLight)
    {
        color += object.color * light.color;
    }
}
複製代碼

  有些引擎(如Unity)在燈光數量多的狀況下,會作一些優化:對全部燈光按亮度進行排序,將最亮的那部分燈光作逐像素計算,中間的一部分作逐頂點計算,排在後面的用球諧函數(SH,Spherical Harmonics)模擬。(見下圖)

  

4.6.3 延遲渲染(Deferred Shading)

   延遲渲染的精髓在於將燈光計算延後,與場景物體數量解耦。

  具體作法是:先將全部物體渲染一遍,但不計算光照,將物體渲染後的像素數據(Position/Normal/DiffuseColor和其餘參數)存於各自的GBuffer;而後,利用這些數據採用後處理方式作光照計算。(下圖)

  

  實現僞代碼:

複製代碼
// 第一遍:渲染物體不帶光照的數據,存於各自GBuffer。
for each(object in objects)
{
    RenderObjectDataToGBuffers(object);
}

// 第二遍:將全部光照對全部像素作計算。
for each(light in lights)
{
    Color color = Color.black;
    for each(pixel in pixels)
    {
        color += CalculateLightColor(light, pixelDatas);
    }
    WriteColorToFinalRenderTarget(color);
}
複製代碼

  因爲最耗時的光照計算延遲到後處理階段,因此跟場景的物體數量解耦,只跟Render Targe尺寸相關,複雜度是O(Nlight * Wrendertarget * Hrendertarget)。

  延遲渲染並無在低端設備支持,它要求OpenGL ES 3.0以上,多渲染紋理以及更多的顯存和帶寬。

4.6.4 基於瓦片的延遲渲染(Tile-Based Deferred Rendering,TBDR)

  針對Deferred Shading的缺點,出現了一種改進方案,它就是Tile-Based Deferred Rendering。此種渲染方式已普遍應用於GPU圖形渲染架構中。

  實現思路:

  1. 將渲染紋理分紅一個個小塊(Tile),一般是32x32。

  2. 根據Tile內的Depth計算出其Bounding Box。

  3. 判斷Tile的Bounding Box和Light是否求交。

  4. 摒棄不相交的Light,獲得對Tile有做用的Light列表。

  5. 遍歷全部Tile,計算每一個Tile有做用的Llight列表的光照。

4.6.5 渲染路徑總結

  前面已經描述了各個方式的優缺點,下面詳細列出它們的性能消耗及平臺要求。

  

  此外,還有Forward+,Physically Based Rendering(PBR),Legacy Deferred Rendering(Unity)等渲染方式,這裏不詳細描述,有興趣的能夠找資料瞭解。

4.7 場景管理和遮擋剔除

4.7.1 場景管理

  場景管理的是在遊戲場景內全部具備空間屬性的物體。目的是爲了快速查找物體,減小物體更新,加快物理碰撞,以及渲染的遮擋剔除。

  常見的場景管理方式有二叉空間分割樹(BSP)/四叉樹(平面空間)/八叉樹(三維空間)/入口(Portail)。

   

  上圖左展現的是四叉樹,圖右展現的是八叉樹。具體的原理和實現方式這裏不描述,有興趣的另行搜索。

4.7.2 遮擋剔除(Occlusion Culling)

  遮擋剔除技術是將不在相機視截體內的物體進行剔除,不送入渲染管線處理,從而減小不少渲染物體。

   

  上圖左是沒有啓用遮擋剔除的場景,右圖是啓用剔除後的場景,能夠看出,超過一半的物體被剔除渲染,優化效果很是明顯。

  與場景管理(4.5)的方式結合,能夠實現快速剔除算法。

  Unity的遮擋剔除須要設置遮擋體(Occluder)和被遮擋體(Occludee)。詳見這裏

4.8 陰影

  陰影的實現方式有不少種,消耗和效果各異。

4.8.1 貼圖陰影

  貼圖的方式最簡單,作法是製做一張陰影紋理,放到物體腳下(下圖),跟隨物體一塊兒運動。

  

  貼圖陰影渲染很是簡單,只須要兩個三角面,適用於低端機型。

  若是地面是起伏不平的,貼圖會被地面遮擋,能夠將陰影貼圖用貼花技術緊貼地面。

  但貼花依然有可能跟其它動態物體發生異常遮擋。

4.8.2 Projector(投射陰影)

  Projector技術是預先指定光源位置和截頭體(Frustum),而後算出物體在其它物體的投影。(下圖)

  

  它能夠將物體投影到任意平面上,但跟貼圖陰影同樣不能表達被投影物體的輪廓。適用於中等畫質效果。

4.8.3 Shadow Map(陰影圖)

  

  陰影圖技術是將物體放入燈光空間(上圖)渲染,獲得燈光空間的深度圖(也稱陰影圖),而後在正常渲染時只要讓某個片元在燈光空間的深度與陰影圖作比較,就可判斷出該片元是否處在陰影之中(下圖)。

  

  Shadow Map技術能夠渲染物體在任意平面的投影,渲染的效果最接近真實世界(下圖),但性能消耗會高不少,它會增長Draw Calls,增長顯存佔用。一般適用於高端機型或重要角色。

  

4.8.4 陰影的分級策略

  ShadowMap效果最好,但最耗性能,而貼圖方式恰好相反。

  這就要根據項目具體狀況作出分級策略,針對不一樣畫質不一樣重要程度的物體採用不一樣的陰影渲染方式。下表是遊戲Z的分級策略。

  

4.9 帶寬優化

  帶寬優化的目的是減小CPU與GPU之間的數據傳輸。

4.9.1 LOD(Level Of Detail)

  LOD即細節層次,根據物體在畫面的大小選用不一樣級別的資源,以減小渲染和帶寬的消耗。

  LOD在圖形渲染中應用普遍,適用的對象有模型LOD,地表LOD,材質LOD,植被/樹LOD,燈光LOD,紋理LOD(MipMaps)等等。

  下圖所示的是物體離相機愈來愈遠,採用不一樣LOD的物體,其中LOD0精度最高,LOD2精度最低。

  

  Unity引擎是經過LODGroup組件實現模型LOD的。(下圖)

  

4.9.2 GPU Instance

  GPU Instance技術應用於繪製相同Mesh的多個實例,每一個實例均可以有獨自的參數(如Color/Position/Scale等)。這種技術能夠減小帶寬,只需傳入一份Mesh數據,就能夠繪製任意多個實例。

  經常使用於繪製建築,地表裝飾物,粒子,草,樹,植被等等。

  

  上圖所示的場景共有1900多個小球,但Batch數只有24,這就是開啓了GPU Instance的效果。但它對平臺有必定要求:

  

4.9.3 GPU Skin

  CPU Skin最基本的角色動畫實現方式,它能夠方便地實現很複雜的動畫操做,如融合/漸隱/組合/串接等等。但它的缺點也顯而易見,佔用大量CPU計算性能,難以並行計算,每幀需傳送模型頂點數據到GPU,增長帶寬負載。

  GPU Skin的作法是將骨骼矩陣列表做爲Uniform傳入Shader,而後在Vertex Shader中對模型頂點進行蒙皮計算。它能夠並行計算,減輕CPU負載,此外,因爲每幀傳入GPU的數據是骨骼矩陣,不是頂點數據,極大下降了帶寬負載。

  固然,GPU Skin也有缺陷。它會提升GPU負載,最高骨骼數每每受限於平臺(如OpenGL ES 2.0不能超過250個Vector4數據量),並且也不利於作複雜的動畫操做。

  Unity引擎能夠在PlayerSettings面板開啓GPU skin(下圖)。

  

  此外,GPU Skin還能夠結合GPU Instance技術,以渲染大量相同角色的骨骼動畫。詳見這裏

4.9.4 GPU粒子

  GPU粒子的優缺點和GPU Skin相似,支持粒子的並行運算,減小帶寬負載,但它一樣難以實現粒子的高級特性(軟粒子/碰撞等)。

  Unity對模型粒子作了特殊優化,支持GPU Instancing(下圖)。

  

4.9.5 正確使用Buffer標記

  OpenGL的Buffer標記有如下幾種:

GL_STREAM_DRAW
GL_STREAM_READ
GL_STREAM_COPY

GL_STATIC_DRAW
GL_STATIC_READ
GL_STATIC_COPY

GL_DYNAMIC_DRAW
GL_DYNAMIC_READ
GL_DYNAMIC_COPY

  1. DRAW:Buffer數據將會被送往GPU進行繪製。

  2. READ:Buffer數據會被CPU應用程序讀取。

  3. COPY:Buffer數據會被用於繪製和讀取。

  4. STATIC:一次修改,屢次使用。可用於靜態模型頂點數據。

  5. DYNAMIC:屢次修改,屢次使用。可用於帶動畫的模型頂點數據,粒子系統的數據。

  6. STREAM:屢次修改,一次使用。可用於特殊場合,好比編輯器數據。

  Buffer的標記各有用途,因此選擇合適的類型能夠減小帶寬負載和顯存佔用。

4.10 Shader優化

  1. 避免使用耗時的數學運算。如pow,exp,log,sin,cos,tan等等。

  2. 使用更低精度的浮點數。OpenGL ES的浮點數有三種精度:highp(32位浮點), mediump(16位浮點), lowp(8位浮點),不少計算不須要高精度,能夠改爲低精度浮點。

precision mediump float; // Defines precision for float and float-derived (vector/matrix) types.
uniform lowp sampler2D sampler; // Texture2D() result is lowp.
varying lowp vec4 color;
varying vec2 texCoord;   // Uses default mediump precision.

  3. 禁用discard操做。緣由見4.2.2。

  4. 避免重複計算。

複製代碼
precision mediump float;
float a = 0.9;
float b = 0.6;

varying vec4 vColor;

void main()
{
    gl_FragColor = vColor * a * b; // a * b每一個像素都會計算,致使冗餘的消耗。
}
複製代碼

  5. 向量延遲計算。

複製代碼
highp float f0, f1;
highp vec4 v0, v1;

v0 = (v1 * f0) * f1; // v1和f0計算後返回一個向量,再和f1計算,多了一次向量計算。
// 改爲:
v0 = v1 * (f0 * f1); // 先計算兩個浮點數,這樣只需跟向量計算一次。
複製代碼

  6. 充分利用向量份量掩碼。

highp vec4 v0;
highp vec4 v1;
highp vec4 v2;
v2.xz = v0 * v1; // v2只用了xz份量,比v2 = v0 * v1的寫法要快。

  7. 避免計算數組下標。在shader使用動態下標會致使較大的開銷。

  8. 警戒動態紋理採樣(Dynamic Texture Lookup,也叫Dependent Texture Read)。也就是說在shader中,紋理座標作了更改,就是動態紋理採樣(也稱依賴式紋理讀取)。在OpenGL ES 2.0的架構下,動態紋理採樣會出現較大的性能問題;3.0則沒有這問題。詳細看這裏

複製代碼
varying vec2 vTexCoord;
uniform sampler2D textureSampler;

void main()
{
    vec2 modifiedTexCoord = vec2(1.0 - vTexCoord.x, 1.0 - vTexCoord.y); // 紋理座標改變了
    gl_FragColor = texture2D(textureSampler, modifiedTexCoord);    // 觸發了Dynamic Texture Lookup/Dependent Texture Read。
}
複製代碼

  9. 避免臨時變量。

  10. 避免使用for等循環語句。能夠嘗試展開。

  11. 儘可能將Pixel Shader計算移到Vertex Shader。例如像素光改爲頂點光。

  12. 將跟頂點或像素無關的計算移到CPU,而後經過uniform傳進來。

  13. 分級策略。不一樣畫質不一樣平臺採用不一樣複雜度的算法。

4.11 UI優化

  UI除了資源優化(2.2)以外,能夠在渲染上作一些優化措施。

  1. 避免不一樣圖集的控件交叉。交叉會增長draw calls,因此要避免。

  2. 動態區域和靜態區域分離。即文字/道具等動態控件放在同一層,而其它靜態的控件儘可能放至同一層,能夠提升合批的機率。

4.12 其它渲染優化

4.12.1 避免後處理

  後處理是場景物體渲染完成後,對渲染紋理作逐像素處理,以便實現各類全屏效果,包含如下效果:

  • 抗鋸齒:Anti-aliasing (FXAA & TAA)
  • 環境光散射:Ambient Occlusion
  • 屏幕空間反射:Screen Space Reflection
  • 霧:Fog
  • 景深:Depth of Field
  • 運動模糊:Motion Blur
  • 人眼調節:Eye Adaptation
  • 發光:Bloom
  • 顏色校訂:Color Grading
  • 顏色查找表:User Lut
  • 色差:Chromatic Aberration
  • 顆粒:Grain
  • 暗角:Vignette
  • 噪點:Dithering

  Unity的後處理棧:

  

  雖而後處理能夠渲染出很是多的很酷很真實的效果,可是消耗也不容小覷。

  特別是在移動端,因爲移動設備硬件架構的特殊設計,會致使更爲嚴重的性能問題,主要緣由是:

  1. 更慢的依賴式紋理讀取(slower dependent texture reads)。關於依賴式紋理讀取的解釋看這裏

  2. 缺乏硬件特性(missing hardware features)。

  3. 額外的渲染紋理解析消耗(extra render target resolve costs)。

  因此移動遊戲要儘可能避免使用後處理。

4.12.2 降分辨率

  降分辨率是最粗暴最有效的提高渲染性能的方法。

  因爲當前不少智能設備分辨率都是超高清,動輒2K以上,但CPU/GPU卻跟不上,若是使用原始屏幕分辨率,就會出現嚴重的卡幀/掉幀現象。

  一般能夠將屏幕分辨率降到一半,這樣渲染紋理/深度Buffer/紋理等等數據均可以縮減到原來的1/4,極大下降了CPU/GPU/帶寬各項指標的消耗。

 

5. 內存優化

  內存優化目的是加快IO,防止卡主線程,防止頻繁操做(建立/刪除)內存,避免內存碎片化和佔用太高。

5.1 緩存法

  與CPU的緩存計算相似,思路是將須要重複建立的對象緩存起來,銷燬時將它放入緩存列表,再次建立時優先從緩存列表中讀取。

  實現僞代碼:

複製代碼
Array<Object> _objectCache;

Object CreateObject()
{
    // 先嚐試從緩存中獲取對象
    if (_objectCache.size() > 0)
    {
        return _objectCache.pop_back();
    }
    return new Object();
}

void DestroyObject(Object obj)
{
    _objectCache.push_back(obj); // 刪除時將其放入緩存列表。
}
複製代碼

  緩存法能夠下降內存的建立/刪除頻率,避免碎片化。

  經常使用於數量多且建立頻繁的物體,如小兵,NPC,血條,特效,道具,各種圖標等等。

5.2 內存池

  內存池技術是現代主流引擎的標配,目的是避免內存碎片化,加速內存分配和管理。

  實現思想一般是由引擎預先建立一塊較大的內存(也可動態調整),這塊內存經過有效的數據結構和算法策略,統一管理小塊內存的分配和回收,併爲邏輯層提供內存相關的操做接口。

  內存池實現的方式不少,各有優劣,不一而足。下圖是其中的一種實現方式:

  

  分配的內存分爲四個部分:第1部分是內存池結構體信息;第2部分是內存映射表;第3部分是內存Chunk緩衝區;第4部分是可分配內存區。更多參看這裏

5.3 資源管理器

  資源管理器是將全部須要用到的文件資源統一管理起來,統一建立,加載,釋放,回收等,爲的是提升複用率,減小資源冗餘和內存開銷,也是現代引擎必備的一個模塊。

  假如沒有資源管理器,勢必會形成資源的冗餘,同一份資源可能存在不少分內存數據(下圖)。

  

  上圖所示中,每一個模型(Model)引用了一份網格(Mesh)內存數據,3個模型實例就有3份Mesh內存數據,形成Mesh內存資源的冗餘。

  而有了資源管理器的統一管理,全部引用到文件資源的實例都指向了同一分內存數據(下圖),避免了內存資源冗餘,下降內存和IO消耗。

  

  資源管理器的實現比較簡單,主要是運用模板將物體類型抽象出來,而後每一個物體類型用一個map<filePath, objectData>的表存儲。具體實現這裏不累述。

5.4 控制GC

  GC是Garbage Collect的簡稱,意爲垃圾回收,是遊戲引擎中採用必定策略回收內存池或託管堆裏的無用內存和緩存區無用對象的一種技術。

  GC機制就是防止內存佔用過多太久,是一種自動調節內存佔用的經常使用技法。

  GC的觸發通常分爲兩種:

  1. 引擎觸發。通常是時間間隔到了,或者內存佔有量到了某個閾值,引擎便會觸發GC。

  2. 用戶調用。一般引擎也提供了API給遊戲應用,以便邏輯層能夠控制GC的時機。例如Unity的GC.Collect()接口能夠觸發GC操做。

  可是觸發GC須要遍歷內存池/託管堆/各種緩存表,還可能引起內存碎片整理操做,因此它須要耗費必定的CPU性能,是引發掉幀和卡頓的罪魁禍首之一。

  那麼,咱們就須要在邏輯層採用一些方法,避免觸發GC,或者減小觸發GC的處理時間。

  經常使用的方法:

  1. 避免頻繁建立/刪除。這個好理解,頻繁建立刪除對象,會引發不少內存碎片和無用對象,增長觸發GC的概率和時間。

  2. 幀更新內儘可能避免臨時對象和建立內存。

  3. for/while等循環內避免避免臨時對象和建立內存。

  4. 儘可能避免申請大塊內存。申請大塊內存會致使內存暴漲,提高GC的概率。

  5. 避免內存泄漏。這個須要每一個技術人員的職業技能和覺悟,也能夠經過一些輔助工具檢查內存泄漏,詳見1.3。

  6. 主動調用GC。好比在進入戰鬥先後,切換場景先後,切換主要界面先後調用GC,能夠必定程度上減小內存佔用,避免掉幀/卡頓。

5.5 邏輯優化

  邏輯優化的目標是儘可能避免無用的內存操做,防止內存泄漏,儘快釋放內存,減小全局變量的使用,關注第三方庫的內存消耗。

 

6. 卡頓優化

  相信不少研發者或玩家,都遇到這種狀況:遊戲大部時間運行都很流暢,但在戰鬥的某些時刻或者打開某些界面會卡一下,甚至卡好久。這個現象就是卡頓。

  引起卡頓的緣由有不少,但主要有:

  1. 突發大量IO。

  2. 短時大量內存操做。

  3. 渲染物體忽然暴漲。

  4. 觸發GC。

  5. 加載資源量多的場景或界面。

  6. 觸發過多過複雜的邏輯。

  避免或者緩解卡頓的技法也是圍繞以上緣由展開。

6.1 降幀法

  跟3.3的方法相似,經過強制下降更新頻率,減緩卡頓的時間。

6.2 攤幀法

  攤幀法就是原本須要在同一幀處理的邏輯分爲若干份,分攤到若干幀去處理,從而緩解同一幀的處理時間,減緩卡頓現象。

  例如,原本在同一幀須要建立10個小兵,這個極可能會引起卡頓,那麼能夠每幀只建立2個,分攤到5幀建立完。

  適用此法的還有資源的加載,AI的更新,物理的更新,耗時邏輯的處理等等。

  此外,還能夠用預處理(3.2),主次法(3.4)來避免卡頓。

6.3 限制數量法

  若是降幀法,攤幀法,預處理,主次法都沒法解決現象,卡頓緣由又恰好是由於物體數量過多,那麼限制數量就很是有必要了。

  作法就很是簡單,當場景內建立某種物體(角色,特效,血條等)的數量到底最大值時,便強制再也不建立。

  此法可能會引發邏輯的一些錯誤和很差的遊戲體驗,需謹慎使用和處理。

6.4 邏輯優化

  若是卡頓是邏輯過於複雜引發的,就須要針對性地優化邏輯。每一個項目的邏輯不同,這裏沒法給出具體的優化措施。

6.5 IO優化

  因IO慢引發主線程等待,從而致使遊戲卡頓的現象很是廣泛,下面有一些經常使用的優化技法。

6.5.1 預加載

  將耗時的IO提早到某個時刻(遊戲啓動時,場景加載時,進入主界面時等)加載,好比有些角色資源大,能夠在加載戰鬥場景時提早加載,以避免戰鬥過程當中卡頓。

6.5.2 異步加載

  將IO異步化,以免卡主線程。此技法應用很是廣泛了,再也不累述。

6.5.3 壓縮資源

  將原本零散的文件壓縮成單個文件,或者對大文件利用必定算法(如哈夫曼編碼)壓縮,減小文件大小。這樣也能夠下降IO時間。

  固然,壓縮資源也有反作用,需佔用多一分內存,解壓縮過程也要耗費額外的CPU。

6.5.4 多級緩存

  咱們都知道CPU的頻率是最高的,目前家用PC的主頻可達3.2GHz甚至更高,CPU內有L1~L3緩存,它們速度略有差異;內存的存取速度遠低於CPU,通常是2~3GHz,約是CPU的1/10。硬盤存取速度又遠低於內存,廣泛是0.1Gb/s,遠低於內存讀取速度。而網絡更慢,目前即使是光纖,也不過0.02Gb/s。

  一般咱們能操控的是內存/磁盤和網絡的數據,因此只要關注它們的速度,它們的速度關係大體以下圖。

  

  因此,多級緩存策略應運而生。

  作法跟緩存法相似,只是多了層磁盤緩存,實現僞代碼:

複製代碼
map<string, ObjectType> _memoryCache;

ObjectType CreateObject(string objectPath)
{
    // 1. 先嚐試從內存緩存中讀取,有就直接返回。
    if (_memoryCache.count(objectPath) > 0)
    {
        return _memoryCache[objectPath];
    }

    ObjectType obj = NULL;
    // 2. 再嘗試從磁盤加載。
    if (FileExisted(objectPath))
    {
        obj = LoadObjectFromFile(objectPath);
        _memoryCache[objectPath] = obj;
        return obj;
    }
    
    // 3. 最後才從網絡下載
    DownloadObjectFromNet(objectPath);
    obj = LoadObjectFromFile(objectPath);
    _memoryCache[objectPath] = obj;
    return obj;
}
複製代碼

6.5.5 控制Log

  遊戲的Log一般會隔一段時間存檔,若是邏輯處理很差,極可能引起卡頓。好比,每幀輸出大量調試log,會引起頻繁存檔。

  遊戲Z在早期,也曾發生卡頓現象,後來經Profiler分析發現是Log存檔引起的。

  因此,有必要對Log作出一些優化。

  1. 避免幀更新輸出Log。防止Log數據迅速膨脹引發頻繁存檔或增長存檔時間。

  2. 改進Log存檔機制。能夠適當改進Log存檔機制,好比每隔多少時間存檔一次,或者Log數據到達必定量級觸發。

  3. 創建Log等級。能夠將Log分爲Info,Warning,Error幾個級別,不重要的log不存檔。

  4. 異步存檔。將存檔Log的邏輯防止單獨的線程,防止卡主線程。

  5. 避免無用的log。這就要在邏輯層控制log輸出,避免無效的log。

6.5.6 JSON代替XML

  遊戲數據存儲通常有兩種:二進制和文本格式。二進制格式數據量最小,但可讀性和擴展性差,適合存儲模型/紋理/字體/音頻等數據。文本格式的特色跟二進制恰好相反,適合存儲配置信息。

  最多見的文本格式有JSON和XML兩種,其中JSON對比XML有諸多優勢:

  1. 數據量少。表達一樣的數據,JSON格式能夠比XML少40%(見下)。

複製代碼
<?xml version="1.0" encoding="utf-8" ?>
<country>
  <name>中國</name>
  <province>
    <name>福建</name>
    <citys>
      <city>福州</city>
      <city>南平</city>
    </citys>    
  </province>
  <province>
    <name>廣東</name>
    <citys>
      <city>廣州</city>
      <city>深圳</city>
      <city>梅州</city>
    </citys>   
  </province>
</country>
複製代碼
複製代碼
{
    name: "中國",
    provinces: [
      { name: "福建", citys: { city: ["福州", "南平"]} },
      { name: "廣東", citys: { city: ["廣州", "深圳", "梅州"]} }
    ]
}
複製代碼

  2. 可讀性更佳。上面兩段分別是XML和JSON表達相同的數據,誰可讀性更佳一目瞭然。

  3. 更快的解析。JSON由於數據量更小,IO也會更快,解析速度固然也更快。

  每一個遊戲都有大量邏輯數據須要存檔,好比角色信息,技能信息,場景信息,配置信息等等。這些數據若是適合用文本格式存儲,首選JSON無疑。

6.6 使用進度條

  若是上面那些章節都沒法解決卡頓現象,能夠嘗試使用進度條。

  思路是將卡頓邏輯抽離出來,分紅若干階段(step),每完成一個step,給一幀時間刷新UI進度條。固然也能夠用異步方式實現。

  僞代碼:

複製代碼
HandleStep1();
RefreshProgressBar(1 / n);
WaitForNextFrame();

HandleStep2();
RefreshProgressBar(2 / n);
WaitForNextFrame();

...

HandleStepN();
RefreshProgressBar(1);
WaitForNextFrame();
複製代碼

 

7. 耗電優化

  遊戲耗電和遊戲卡並沒有必然聯繫,有些遊戲在某些設備上雖然運行很流暢,但發現耗電很厲害,玩了不到半個小時,電量已經出現警報。

  遊戲耗電的緣由主要是由於:CPU佔用廣泛高,內存操做頻繁,磁盤IO頻繁,渲染消耗廣泛高,致使帶寬負載和GPU消耗高。

  前面介紹的章節基本能夠下降耗電,也是優化耗電的必要措施。尤爲是如下章節對耗電優化更明顯:

2. 資源優化

3.3 限幀法

3.4 主次法

3.6 引擎模塊優化

4 渲染優化

5.4 控制GC

6. 5 IO優化

  除了以上章節,還能夠用動態調整幀率和畫質的優化技法。

7.1 動態調整限幀

  遊戲邏輯一般能夠獲取當前設備的電量,若能夠,則每隔一段時間獲取一次電量信息,能夠統計出單位耗電量,若是發現單位時間耗電量太高,而遊戲幀率又很高(好比大於50),能夠主動下降幀率(好比30)。

7.2 動態調整畫質

  作法跟動態限幀相似,只是調整的是畫質等級,而不是幀率。固然也能夠一塊兒結合使用。

 

8. 網絡優化

  網絡優化的目的是讓網絡包更小,響應更及時,消耗更少流量,不卡主線程。

8.1 減小無用字段

  網絡包中一般包含了不少信息,諸如角色位置,朝向,狀態等。

  若是是2.5D遊戲,則位置z份量能夠棄掉;朝向只在xz平面上,因此只須要發送RotationY。

  經過這種減小無用字段,能夠必定程度上下降網絡包大小。

8.2 下降字段精度

  一般邏輯裏的不少信息都是4字節,包括角色位置,朝向,技能或Buff信息等。但不少時候,這些信息不可能達到4字節數的最大值,能夠壓縮至2字節甚至1字節。

  好比,一樣是位置,場景的尺寸一般在2字節數的表示範圍內(-32512~32512),能夠將位置的x/y/z壓縮至2字節發送。一樣地,朝向RotationY能夠2字節表示。

8.3 避免重複發送

  遊戲網絡模塊須有效限制部分協議在短期內重複發送,例如玩家在短期內按了不少次抽獎按鈕。

  因此須要一種機制來限制。好比能夠在網絡協議定義時,加個標記,代表該協議不能在某個時間段內重複發送。

8.4 網絡異步化

  開闢獨立的線程處理收發網絡協議包,是遊戲常見的優化手段,能夠避免與主線程相互等待。

8.5 壓縮無效字節

  壓縮無效字節是指經過一種方式剔除每一個字段內高位全0的數據。

  好比角色等級50,若是用int32表示,是00000000 00000000 00000000 ‭00110010‬,高位3個字節全是0,能夠壓縮至1字節。

  這裏有一種壓縮字節的方法,跟utf8編碼方式相似。

 utf8的編碼方式(x表明有效位):

1字節(最大有效位7)  :0xxxxxxx 
2字節(最大有效位11):110xxxxx 10xxxxxx 
3字節(最大有效位16):1110xxxx 10xxxxxx 10xxxxxx 
4字節(最大有效位21):11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
5字節(最大有效位26):111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
6字節(最大有效位31):1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 

  好比角色等級50,有效位是6,用1字節便夠了,壓縮成00110010‬(紅色是壓縮位標記)。

  若是是數字1000,int32表示爲00000000 00000000 0000‭0011 11101000‬,有效位是10,須要2字節編碼,壓縮後是11001111 10101000‬(紅色是壓縮位標記)。

  採用這種壓縮方式,廣泛能夠用1~2個字節取代4字節數據,壓縮效果比較明顯。

8.6 壓縮協議包

  8.5壓縮的是字段內數據,每一個數據包其實有不少相同的數字,能夠用目前主流的壓縮方法再對網絡包作一次壓縮。

  遊戲最經常使用的壓縮方法是zlib開源庫,還能夠用lz4方法。具體實現能夠另外尋資料,這裏不詳述。

 

9. 總結

  看到這裏,基本也就結束本文內容了,但願此文能給各位遊戲開發這帶來實質的優化技法或者新的思路。

  固然還須要說一下性能優化常見的一些誤區。

9.1 優化誤區

9.1.1 文件小的圖片佔用內存也小

  有些人覺得圖片文件小,它佔用的內存也會小,因此有些人將圖片轉成高壓縮率的jpg格式。

  那麼這個見解和作法是否穩當呢?

  根據2.1章節,能夠看出圖片佔用的內存大小跟圖片的尺寸和像素格式相關,跟文件格式不要緊。

  因此,圖片轉成jpg只是壓縮了文件大小,但並不能下降內存開銷!

9.1.2 合批的數據越大越好

  有人會認爲即然合批可以下降渲染消耗,是否是讓合批後的數據越大越好,以便更多地下降Draw Call呢?

  答案是否認的。

  緣由有二:

  1. 移動遊戲的模型索引一般作了優化,只用16位表示,也就是說若是合批後的頂點數超過65025,便會越界,致使渲染異常。

  2. 太大的數據量可能沒法充分利用LOD,遮擋剔除等技術,致使過多的數據送入GPU,反而增長帶寬和GPU消耗。

9.1.3 片元等同於像素

  片元(fragment)是GPU內部的幾何體光柵化後造成的最小表示單元,它通過一系列片元操做(alpha測試,深度測試,模板測試等)後,纔可能最終寫入渲染紋理成爲像素(pixel)。

  因此,片元不是像素,但有機率成爲像素。

 

 

================ 全文終 ================

相關文章
相關標籤/搜索