KlayGE從4.0開始引入deferred rendering層(DR),而且這幾個版本都在持續地改進,以提升性能和下降使用難度。在即將發佈的4.4裏,deferred rendering更是往前跨了一大步,實現了一個初步的Tile-based Deferred Rendering(TBDR)。和常見的TBDR不一樣之處在於,這裏的方法只須要SM3。(其實SM2也沒問題,只是若是光源較多,會遇到指令長度限制)算法
在傳統的deferred rendering中,每一個光源須要和每一個像素作一次相交測試,測試經過的才計算光照。這個相交測試通常經過light volume的方式進行優化。但最終仍然須要對每一個light畫1次。也就是說,每一個像素須要對每一個光源讀取一次G-Buffer,計算一個光照,並作一次blend寫入。這個帶來的帶寬開銷遠大於forward rendering,使得在低帶寬的設備,尤爲是手機平板平臺上,幾乎沒有辦法使用deferred rendering。框架
Tile-based的核心思想是,把G-Buffer劃分紅等大小的tile(通常會用32×32),每一個tile會維護一個列表。對於每一個tile,從depth buffer能夠算出它的bounding box。把光源分紅N個一組,每一個光源和每一個tile的bounding box作一次相交測試,並把光源id放入tile的列表中。在lighting的時候,一個pass只須要讀取一次G-Buffer,就能計算列表中全部光源的光照。而輸出的時候只作一次blend寫入。由於在GPU寄存器上讀寫的速度遠高於讀寫顯存,這麼作直接能把帶寬減小N倍。用流程圖來表示,就是:性能
從這個流程圖能夠很明顯看出二者的大致框架相同,但Tile-based能把N個pass變成shader裏的N次循環,因此就有機會提升效率。測試
這裏來一個帶寬的定量比較。假設G-Buffer由2個ABGR8組成,depth buffer的格式是float,光源數量是L、分爲N個一組。Lighting buffer的格式是ABGR16F,shading buffer的格式是B10G11R11F。那麼能夠比較一下平均一個pixel的帶寬。優化
Deferred | Tile-based Deferred | |
---|---|---|
Tiling讀取帶寬 | 0 | (32*32 + 2*(16*16 + 8*8 + 4*4 + 2*2)) / (32*32) * 4 = 6.66 |
Tiling寫入帶寬 | 0 | 2*(16*16 + 8*8 + 4*4 + 2*2 + 1*1) / (32*32) * 4 = 2.66 |
Lighting讀取帶寬 | L * (4 + 4) + L * 8 = L * 16 | L/N * (4 + 4) + L/N * 4 = L/N * 12 |
Lighting寫入帶寬 | L * 8 | L/N * 4 |
Shading讀取帶寬 | 8 | 0 |
Shading寫入帶寬 | 4 | 0 |
總和 | L*24+12 | L/N*16+9.32 |
DR不須要tiling,因此tiling部分的帶寬都是0。TBDR的shading已經合併到lighting pass了,不須要獨立的shading pass。同時也是由於已是shading了,不須要把diffuse和spacular分開,也不須要輸出到ABGR16F四個通道(三個diffuse,一個specular的亮度),只要RGB三個通道足矣。google
因爲tiling和shading的帶寬相比之下已經微不足道了,因此這裏主要就看lighting部分。若是N比較大,能夠很明顯看出lighting的帶寬佔用會明顯減小。在KlayGE 4.4的實現裏,N=32,已經能把帶寬消耗減小到相似forward的水平了。spa
TBDR的實如今網上已經有不少文章和代碼,這裏就只討論一些細節。.net
Tiling的方法很簡單,相似於mipmap那樣,把depth buffer每次減小1/2。但須要保存的是min和max兩個depth值,這樣才能構成bounding box。這個bounding box其實是一個斜的視錐。能夠用個off center的frustum構造方法求出來。詳見Intel的例子。3d
在求交計算的時候,point light能夠用球,spot light暫時用的是AABB。(其實spot最好使用cone和frustum求交,但我沒找到相關算法)。在shader裏把它們和tile的frustum作求交測試,也就是把原先CPU上的view frustum culling搬到shader裏,per-tile執行。code
重點在於,通常提到TBDR的地方說的都是用compute shader,至少也是具備任意寫入能力的pixel shader 5,才能把這個列表保存到相似OIT的per-pixel linked lists裏。但這樣的話就失去了對老硬件和移動平臺的支持(雖然下一代移動GPU就能支持完整的D3D11,但普及尚需時日)。KlayGE 4.4的方法是相似於light indexed rendering的作法,用個常見的ABGR8格式,每一個light佔用一個獨立的bit組成32位的mask來保存這個列表。這麼一來,就能夠把32個光打包成一組,用這個固定長度的bit「列表」保存哪些光源對這個tile有影響。在不支持位運算的硬件上,能夠用除法和求餘來模擬出bit and操做,因此也能獲得某個bit是不是1。
最終實現的TBDR只須要用到SM3甚至SM2的shader,帶寬減小32-64倍,瓶頸移到了計算上。並且,和原先的deferred rendering相比,只改了不到100行C++代碼和100多行shader代碼。任何一個deferred框架均可以輕鬆遷移到這個方法上。同時性能提高也是很明顯的,目前能夠輕鬆在低端硬件上實時渲染大量光源。
這裏測試了720p的分辨率上,sponza場景在高端的GTX680分別使用DR和TBDR,每一幀所花費的毫秒數,橫軸表示光源數。
和理論分析同樣,隨着光源的增多,TBDR花費的時間也逐漸減小,到1024個光的時候已經能少20%了。注意這裏的TBDR只是個算法驗證,還沒到優化的階段。而DR已經通過多輪優化了。
在低端的NVS4200M上,這個差距就更明顯了,差距能夠超過50%:
在KlayGE 4.4發佈前,我會進一步優化TBDR,但願能有更好的表現。KlayGE 4.5中打算實現個compute shader的版本,把1024個light分紅一組,基本上能夠在一個pass算完全部的光照和着色。之後也會考慮更新的clusted deferred。
本篇講了一個只須要SM3的TBDR,下一篇會將一些Deferred框架的其餘改進。