gkENGINE上次總結了一篇開發總結(上)。然后二進制DEMO發到了OPENGPU上,算是引起了挺多的關注。
友情鏈接一下,openGPU帖子:
效率這個問題被很多大牛提及,因此,正在撰寫的開發總結(下)被我停了下來。認真的分析渲染流程,找到性能瓶頸,嘗試修改,突破。再分析新的性能瓶頸,嘗試修改,突破...
整個優化完成下來,渲染器結構也重構了不少,接下來繼續總結渲染流程應該更加得心應手。
題外話:gkENGINE之前的二進制DEMO已經上傳至項目主頁
https://gkengine.codeplex.com/
gkENGINE 項目已經決定開源,代碼正在整理中,准備工作完畢后就上傳至codeplex托管,歡迎到時各位朋友捧場,相互學習!
終於,經過兩周的業余時間,把DEMO的開場鏡頭的效率提升到了240%。期間的一些有意思的分析和優化方法,值得記錄一下。
MISSON START
當時是一個周末,先在程序中將DEMO中截圖的那個鏡頭鎖定了下來。然后開始用Intel GPA這個圖形分析工具開始分析。
插一句,GPA是Intel出品的一款圖形分析工具。類同DirectX SDK自帶的PIX和 Nv著名分析工具PerfHUD。GPA的最大優勢是使用便捷,穩定性高。個人認為這是PIX和perfHUD的弊端。因此一般的簡單分析任務我都選用GPA來完成。
工具的下載頁面在這里:http://software.intel.com/en-us/vcsource/tools/intel-gpa
不過要說的一點是,雖然GPA已經更新到了2013 R2版本。但是個人覺得最好用的還是歷史的4.3版。對nvidia, ati的顯卡支持得不錯。后繼版本的支持就顯得不那么友好了。並且坑爹的是,好像Intel官方不再提供以前的版本,需要在其他網站搜索下載...
接下來,便得到了精確的GPU時間數據和各階段的詳細資源:
渲染效率:GTX560 104frame/s 9.6ms/frame
這是對0425DEMO版本,第一個鏡頭的GPA時間分析。簡要分析,將一些不太合理的瓶頸標紅,如下:
SSAO:全屏的AO計算,占用了整體場景着色時間的一半。
SHADOWMASKGEN:陰影mask的生成,總體消耗的時間和SSAO等同。
POSTPROCESS:FOG, HDR, DOF等后處理效果,消耗時間也很高。
REFLECTMAP: 反射圖的生成,在此場景中沒有水面,REFLECT MAP的生成應該被裁剪掉。
第一輪,與分辨率的戰斗
以上的瓶頸,都是后處理算法,都是像素計算密集型的渲染算法,因此,如果能夠在基本保證渲染質量的情況下,顯著的降低像素計算復雜度,那么性能將會得到成倍的提升。
1. SSAO優化
SSAO的shader,匯編指令169, 11個采樣指令。1280x720分辨率,每幀執行92W次。根據場景復雜度,優化了一下旋轉紋理的采樣方式,使用半分辨率的SSAO處理(降采樣)。可以將計算量降低為1/4,而質量下降很低。
2. ShadowMask優化
采用相同策略,使用半尺寸渲染,將計算量降為1/4。不過這時在后面的着色階段,會產生渲染錯誤。
如圖,由於MASK采用半尺寸,因此在全尺寸的着色階段,會由於線性采樣,在陰影和受光的交界像素處采樣到非陰影值(樹干的邊緣,后面的茅屋陰影有非陰影白邊)。
解決的辦法是,在着色階段,在采樣點都右下方像素多采樣一個陰影值,兩者取最小值作為當前像素的陰影值,過濾掉這個渲染錯誤。這個解決方案也有必然的弊端:可能會在本身沒有陰影的地方產生一定程度的陰影黑邊。但是相對白邊,黑邊造成的瑕疵完全可以接受。
3. POST PROCESS優化
之前的POSTPROCESS有很多RT來回倒騰的操作(紫色的矩形塊)。經過RT的合理分配和順序調整,可以去掉一些RT STRETCH的操作,提高POST-PROCESS的效率。
4. 終極優化 - 可變渲染分辨率
之前都是針對各種特性渲染的降采樣,來降低像素計算壓力。但是在移動平台上,提升依然不是太明顯,因此,需要一個終極的解決方案。經過PHOTOSHOP里測試,使用3/4的尺寸渲染,然后“放大”到全尺寸。最終的畫面質量下降不是太大。但是像素計算量能直接下降為接近1/2。帶來的性能提升是十分顯著的。因此在之前的texture管理器中添加了一個scale屬性。對除BACKBUFFER之外的所有紋理進行降采樣處理。渲染完成后,STRETCH到BACKBUFFER上。
第二輪,解決新問題
第一輪優化暫告一段落,和分辨率的斗爭結束了。渲染效率直接提升了接近100%。當時就在OPENGPU上補充了一個跟帖,效率的確是提上去了,不過也有壇友提出質量下降的問題。
1. 為低分辨率渲染添加銳化pass
如上圖所示,的確,使用3/4的渲染分辨率,少了近一半的像素,質量下降難免,如壇友所說,結果像是圖片經過了質量壓縮。但是這個下降是否可以再補償回來一些呢?
經過一些調研,輕微的尺寸縮放可以通過銳化來補償。於是開始嘗試之前做過的銳化算法,將圖像進行微弱的高斯模糊,再利用模糊結果和原圖進行線性外插,強化像素的“反差”,達到銳化目的。
color = lerp( blur, curr, sharpvalue ); // sharpvalue取值大於1
2. 使用手動MIPMAP解決地形顆粒感過重的問題
對於有壇友提出的顆粒感過重的問題,因為terrian的多層混合是直接在shader中計算的,由於紋理的重復采樣是直接在shader總通過frac得出,因此打開mipmap會有采樣錯誤(由於frac計算出的texcoord不連續),之前圖省事直接關閉了mipmaping。因此這里用了一個簡單的方法,解決這個問題:利用像素的線性深度,手動計算應該采樣的mipmap層數(避免使用自動的ddx計算,造成地塊間的不連續值),然后使用texlod來取得對應Mipmap的值。
這時再觀察GPA的數據。
這時之前的幾個瓶頸已經被壓到合理的消耗范圍。渲染時間大部分集中在了shadowmap生成,zpass, general pass上。這算是一個合理的渲染管線消耗分配了。
但是也可以注意到,圖標中標黃的兩個消耗塊,占據了相當大的時間,已經超過了SSAO和SHADOWMASK的消耗。
這兩個消耗,就是地形系統中占屏幕像素最大的那個block。分析他們的shader assembly,發現每個像素采樣的次數達到了驚人的26次!(zpass 7次, general pass 19次)
3. tex2dlod和tex2dgrad的選擇
而仔細看卻發現tex_ld, tex_ldl的次數卻沒有那么多,通過搜素發現,原來tex2dlod這個函數可以顯式的指定lod層數,消耗比tex2d, tex2dgrad都要大。在GPA中會顯示兩次采樣指令。
因此,將tex2dlod的方式改為tex2dgrad, 手動計算ddx送入插值而非直接指定mip層數。像素采樣次數直接砍半。
4. 高光紋理合並到diffuse的alpha通道
同時,發現地形紋理使用高光貼圖稍微有些浪費,索性直接將高光值做成單色存入diffuse的alpha,砍掉采樣高光紋理的消耗。
至此,地形block的采樣次數從26次降低到了9次,地形block的渲染消耗直接降到了50%。
第三輪,引入混合渲染管線
經過前兩輪優化,基本上性能已經被榨干至極限了。不開啟銳化的情況下,在720p分辨率在GTX560上已經能跑到260FPS以上,接下來想要在保證畫面質量的情況下,進一步嘗試一下優化,突破點大概只有幾點了:
- 1. 降低DP
- 2. 降低shader復雜度
對於第二點,要保證渲染效果不變,這將是個漫長的優化-測試的循環過程。
對於第一點,目前渲染管線是Deferred Lighting(延遲光照),CryEngine3使用的是這個方式。
優點:在擁有延遲渲染解耦光照運算的優勢下,能保證主光源豐富的材質效果,同時獲得一個低帶寬開銷的渲染流程。
缺點:所有不透明物體需要渲染兩次:Zpass一次,輸出法線和線性深度,GeneralPass一次,利用生成好的光照數據,和主光源,進行傳統的材質運算。
而Crytek在GDC2013的演講中提出了Hybird Deferred Shading的概念,將之前實現的延遲光照和傳統的延遲渲染進行混合,對於普遍材質使用延遲渲染方式,只需要一次渲染調用。而對於復雜材質,和以前一樣走延遲光照的渲染流程。
因此,我決定先引入傳統的DeferredShading實現,先觀察效率,並做成可以實時靈活切換渲染管線的架構,再考慮進一步的混合渲染方式。
而要引入混合可切換的延遲渲染管線,就需要對之前的渲染流程進行改造。之前的流程是
shadowmapgen -> zpass -> ssao -> deferred lighting -> shadowmask -> general pass -> fog/hdr/dof -> msaa -> output
對於傳統延遲渲染,大體流程都是需要的,但是在zpass, deferred lighting, general pass階段是需要有不同的算法流程
於是我將各個階段抽象成IBasePipe, 通過策略模式,將算法包裝進Pipe的派生類,然后主渲染流程調用各個pipe執行,選擇對應的pipe實現進行渲染算法的組織。
因此,渲染流程變為了
pipe[shadowmapgen] -> pipe[zpass] -> pipe[ssao] -> pipe[shadowmask] -> pipe[deferred lighting] -> pipe[general pass] -> pipe[postprocess] -> output
同時,延遲渲染還需要新的zpass和最后的合成pass的shader,同時,因為不能使用獨立的generalpass了,因此之前的一些特殊材質效果一定需要損失。
對於G-BUFFER,之前的配置是DEPTH|R32F + NORMAL+GLOSS|RGBA8。而延遲渲染需要記錄物體的材質信息,所以至少需要多添加一層ALBETO顏色信息,所以GBUFFER在原有的基礎上擴展了一個ALBETO DIF+SPEC|RGBA8的MRT。用於輸出ALBETO顏色和單色的高光。
對於物理屬性,諸如:FRESNEL等,暫時沒有寫入。后期考慮壓縮NORMAL,在NORMAL中多開辟一個通道來存儲。
渲染流程改為deferred shading后,DP少了一半,G-BUFFER的帶寬壓力增加了50%。但總體下來效率還是提高了5%左右。
可惜的是,deferred shading要求更加統一的材質屬性設置。所以,之前為延遲光照設置的材質屬性,在延遲渲染下表現有了差異。考慮到提升並不明顯,因此還是默認使用deferred lighing渲染管線。
MISSION ACCOMPLISHED
任務完成,總結一下最終的效率。
渲染配置:
1280 x 720分辨率, 35.9W三角面, 362 DrawCall
特效全開
0.75的渲染尺寸,ssao, shadowmask一倍降采樣
測試結果:
測試平台 | 幀率 | 幀時間 |
Intel i5 2500K & Nvidia GTX560 | 241FPS | 4.14ms |
Intel i7 3720QM & Nvidia GT650M | 140FPS | 7.14ms |
Intel i5 2500K & Intel HD Graphics 3000 | 30FPS | 33.33ms |
效果對比:
最下面的兩張渲染結果分別是全渲染尺寸 和 shadow, ssao全尺寸的渲染結果。基本代表了優化開始時的渲染質量。