關於Unite
Unite大會是由Unity舉辦的全球開發者大會,至今已有10年的歷史。Unite現已成為遊戲行業,VR/AR行業中最具有權威性和影響力的活動。
本文是Unity研發大牛姚霄凌在Unite大會的演講,以下是正文:
來的人比我想像中多一點,早上很早,自己也很早起來。我看大家今天有一整天都在這裡。我就講一下發布一年多來,我們做了什麼。將來還準備做哪些。
先介紹一下我們自己,Terrain Team有兩個人,都是在上海,我還有蔡原新,今年我還是要自己講一下。我們Terrain Team最近就集中在這個上面,新的功能暫時就不做了,就只是修一下Bug。平時的話做一些引擎底層的優化。
一、GPU Instancing:The past year…
過去一年Unity 5.4剛發布,5.5快要發布的時候,那個時候是我們GPU Instancing第一次在5.4裡面發布,那個時候大家用得比較少。這些新的功能為什麼要去做?也是因為在Unity官方論壇上收集到了很多,主要是國外開發者他們用了Instancing之後,他們的一些問題。然後其實開發者那邊比較難解決,還是需要我們自己引擎這邊需要為大家做一些事情。其實就相當於大家提問題,我們幫著做。
1、新API:DrawMeshInstanced
最重要的就是5.5添加了新的API(DrawMeshInstanced),是大家呼聲最高的一個,它本身很簡單,就是從函數的籤名可以看到,這種材料是shader,關鍵是你提供了一個數組,這個數組就是每個Instance的變換矩陣的取證。其實很簡單。變換矩陣大家應該很熟悉,可以用TRS這個函數來生成出來,我們為什麼要去做它?好處有很多,原來5.4剛剛發布Instancing的時候,純粹是靠renderloop裡面,我們添加了一個Instancing。意思就是說你要畫五百個Instancing,你就必須要畫五百個GameObject,這個開銷是非常非常大的。很多情況下你放了那麼多GomeObject,你的CPU開銷就上去了。這樣整體來說CPU這邊會有一點點提升,但是會很小。有了這個GPU Instancing,首先renderloop不是很穩定,經常哪一個點就break了,也不知道為什麼。但是新的版本裡面加了一個功能,可以知道上一個和下一個沒有連在一起的原因。
那麼多的Instancing如果放在renderloop裡面,每一幀會去跟,這個開銷也全都省了,用一個API就好了。我們肯定是保證它的GPU性能是一樣的。有了這個之後,至少看到論壇上大家真正想用Instancing去做一些系統的,比如說我看到一個遊戲是太空中很多飛船戰鬥的一個場面,有很多雷射biu biu biu的那種,然後很多飛船都是用Instancing來做。他們反饋下來是CPU可以節省非常多的時間。壞處就是這個API不是那麼好用,你需要自己手工來寫代碼。因為就只是其中Unity一個數組,這個裡面Unity引擎是不知道的,所以它視錐剃除不會去做。比如說照相機轉開了,看到五百個Instancing裡面只有五十個。如果你需要消除這方面的影響,你需要自己做完剔除再提交,就比較麻煩一點。然後z-efficiency就是排序的問題,一般的話它可以遮擋住後面的物體。如果你想要獲得比較好的z-efficiency,你需要自己去做排序。還有就是每一幀要去調開,之前的邏輯很長,開銷也是蠻大的。
2、新API:DrawMeshInstancedIndirect
這些是問題,然後以後會解決,大家可以看到我們最後我們會有一個新的系統嘗試去解決一些新問題。現在暫時網上我們asset store裡面各種示例可以參考一下,它們做得很好。剛剛那個是5.5的,5.5應該有蠻多的用戶。5.6裡面新加了這個,就是Indirect。Indirect在於兩點:一個是不直接從GPU去生成Instancing的數據,不再是由CPU去告訴GPU數據是怎麼樣;另外一個是也不由CPU去告訴GPU要畫多少個Instancing。這些CPU全都不知道,CPU只是告訴GPU這裡可以看到ComputeBuffer對象。你從這個裡面去拿參數。五個參數,所以這塊ComputeBuffer很小,CPU只是告訴GPU你去從這一塊buffer裡面生成一個drawcall出來,其他的Instance數據從哪裡來都是shader會自己算出來。所以這個函數的籤名就是這樣子,可以看到,這個aabb其實對於API本身來說不是很需要,現在只能暫時放在裡面,以後renderloop重構之後就可以去除了。aabb你們開發者可以知道,你們可以把這個包圍盒告訴unity,然後可以做一個很簡單的camera的視錐剔除。它是需要API去支持的,所以需要你跑在D3D11以上。包括openGL4.3是不支持的,然後安卓的話需要GLES3.1。我相信一年半年以後就可以了,然後IOS Metal應該是支持的。
這個函數跟剛才那個相比,它主要的每個Instancing數據不是CPU告訴它的,而是通過shader裡面過程化地去生成。裡面包含兩個點,一個是用Instancing 0、1、2、3、4你需要過程化地去算出來。你一排的cube,地理坐標一個一個排序,用Instance Id可以去乘20。或者是你可能會有另外一塊buffer,這一塊是裡面放了Instancing的數據,你的shader只是去讀這個數據,然後把它附給每一個Instance。所以這個整合GPU Compute是最合適的,不需要CPU和GPU之間互相讀來讀去。原來那個就很沒有效率,如果你真的能去掉這一塊,相當於你去CPU、GPU做同步,就浪費了一幀兩幀的時間。另外移動平臺芬蘭那邊同事測出來,用Compute還太早。Compute本身可以,但是切換時間還比較久。PC上可以,安卓大多數不支持。
3、新的工作流程:5.6
5.6裡面還有一個新的變化,就是新的work flow,原來你要用的話就比較要建一個shader,5.6裡面一般來說不需要。你點上去,最底下會有選項,你勾上就有Instancing。你如果使用standard shader或surface shader就可以。
相關的還有shader variant stripping。你比如說player的時候,本身shader保存在磁碟上還是蠻費空間的。現在5.6裡面相當於你不用的話都可以被strip掉。策略很簡單,build player的時候會去分析你build的場景。用了這個shader的material,只要用了任意一個,Instancing的variant就會被包含在最後的裡面。在Graphics settings裡面,這些都放在一起會有一個全局的控制。你可以把它強制給strip掉,這樣最終出來的就不支持Instancing。
剛才說那些都是在在線的文檔裡面。第一個草稿其實就是我們原來的5.4的文檔。現在是一個草稿,就算是草稿,你也可以去這個網址上去找,專門有一個草稿欄。關鍵是這個新的API的這個文檔我丹麥同事幫著寫了,裡面有很多的例子,大家可以看一下。
二、GPU Instancing:2017 and forward
1、「GPU Instacing 2.0」
接下來就是講除了這些之外,那些是已經發布的功能。接下來是我現在正在做的,可能在下一個版本,下一個版本2017.1可能delay。2017.1之後的版本,我講的這個應該會上去。GPU Instancing 2.0就是我們在論壇上說到更多別的反饋,或者是我們內部demoteam。我們自己在做地形和數相關的demo,他們也碰到一些問題,然後會要嘗試去解決它。第一個其實也很傻,大家知道Instancing的編譯速度很慢,原來是我們用了很長的數組,我自己測試下來,只是單純用微軟FXC,去編譯一個,一秒和十三毫秒的差別。應該是微軟shader的問題,我們去聯繫過他們,他們這個問題不準備修了,需要我們自己來解決這個問題。
因為有這個,所以你們添加新的Instancing的時候,你們需要考慮,你們每添加一個就相當於多了一個數組,編譯速度就越來越慢。我之前試過,我加了七個然後去編,就很慢。除了編譯速度之外還要考慮constant buffer的大小限制,它不是任意長度。規定是64kb,所以你的數組一多,或者你的數據一多,你需要把它考慮分在其他的constant buffer裡面,比較麻煩,要自己去算。然後maxcount決定了你有多大,比如說你的size是五百,你就算畫十個Instancing,你也是上傳一個五百的buffer,總線帶寬是很浪費的。所以之前你可以在shader裡面制定maxcount有多大,就評估你大致有多大,可以設一個值,這樣的話可以減少一點浪費。但是很難保證在所有情況下都能最大優化。maxcount在OpenGL下,它只有其他平臺的四分之一。為什麼呢?因為openGL下面,它的標準只保證有16KB的標準。一塊顯卡可能只有64KB,但是我們不知道。它之前是寫死在shader裡面,我們只能用一個最小的值。剛才說了,Instancing物體不支持全局光照,因為它需要7個數組,現在不是很現實。所以現在的Instancing都不支持lightmap。我們就會去嘗試去解決剛才所有問題。
目標首先不應該需要為shader的編譯速度擔心。可能之前根本就沒有擔心過,一般來說都非常快。我們希望你們不用再去考慮什麼是maxcount,就是那個數組大小,你們不用去想你這個項目多數情況下能有多大,根據這個再反過來修改shader。這個工作流上就會很慢。我們也希望不要佔太多的總限帶寬。理想情況下你畫五百個Instancing,你就傳五百個,你畫五十就傳五十,我們希望能支持到GI。
方案首先第一步要解決Instancing buffer的布局,有了布局之後,我們相當於把多個array合併成一個array,這樣子才可以動態改變它的長度。不然一個array本身就是一個一個單獨的array,要改變長度的話中間會空出來很多。沒有辦法縮小Constant Buffer它本身的size。另外一個就是用Out-of-Bounds Reads,聽上去蠻嚇人,其實它不會crash,數組長度不用等同於真正綁定的CB(Constant Buffer)大小。
2、Array-of-Struct
先講Array-of-Struct,上面一張是我們現在的布局。可以看到兩個Instancing的數據,一個是藍色world,另外一個橘黃色的是colour。先是五百個world,然後再放第二個Instance數組。代碼上來解釋就更加簡單一點。
知道Unity的人可能會知道,Unity現在shader也不支持寫struct。我們底層代碼要改得很多,要改到能夠正確讀出來每一個struct member的offset。改成這樣以後,我們需要新的宏來聲明。前兩天才剛做完GPU的開銷的測試,我本來期待它要比SOA要好一點,但是事實上完全一模一樣。我猜應該是跟它本身上面的實現方式,硬體這一塊我也不是很懂。如果在座的有知道這一塊的話可以教一下我。它的開銷我要介紹一下,所謂的開銷我們不是去測量用不用Instancing,我們測量的是說,比如說你同樣是訪問一個東西,你從array裡讀出來的性能和你直接去用單個的uniform去讀,相差多少。原來的和SOA是一樣的。CPU端性能更好。CPU端本身Instancing性能就已經蠻好的,但是更好的話相當於大家可以塞更多的遊戲相關的代碼到遊戲裡面。所以還是有一點用處的。
3、Out-of-Bounds Read
另外一個就是Out-of-Bounds Read,顧名思義就是越界讀數組。shader裡面就是數組聲明地越短越好,我們現在用的是2,為什麼不用1。1的話就是只有一個,所以它每次你都只讀到第一個元素,就算你用Instancing ID去讀,每次都是去拿之前的數據,就一定要用2。它會生成出來,我們綁定InstanceBuffer的時候,去讀沒有問題。D3D和Opengl都是支持的,但是Vulkan不支持。其他平臺有可能有些平臺會支持,我不確定Metal支不支持,但是我相當確信支持,但是還沒有來得及做。如果不支持那怎麼辦?我們想你shader在編的時候還是用長度是2,但是編完之後,因為用的是D3D11的compier,其他平臺的都是翻譯過去。所以可以修改數組長度。
性能上會比沒有它要慢一點,但是會和原來一樣,所以也不算太慢。最主要因為是長度只有2,所以大大縮小了shader的編譯時間,大約100×左右的倍率。然後可以在運行時根據批次大小動態調整綁定的constant buffer長度,節省帶寬。我們去調整一個size,因為新的buffer還是要create出來,所以不能每個不同的batch size都創建新的buffer。我們會去取,比如說50個、150個、200個,這個暫時還沒有定。用戶就不需要再去算你的CB Max Size有多大。Open GL的問題就解決了,可以拿到它支持的uniform block的大小,可以獲得跟其他平臺一樣的大小。
4、全局光照支持
有了這個以後,可以做全局光照。我們可以會去做一下壓縮,把float壓縮一下。然後lightmap ST的4個float還是很小的,貼圖還是要一樣的。但是在lightmap的位置就可以不一樣。
5、其他改進
其他的話CPU端的代碼要更優化,並且支持多線程。在CPU端的開銷更加小。相當於來說我會去做半精度浮點類型的優化,這個的話API D3D11都是不支持的。我們現在自己去填buffer,所以可以這樣優化。還有就是寫數據在constant buffer會更緊湊一點。
6、結論
結論就是CPU端的開銷更加小,GPU端的開銷在某些情況下更小,這個某些情況就是指你整體瓶頸在於總線帶寬的時候,GPU端開銷會更加小。然後修改shader更快,支持全局光照。大部分功能正在測試,例如2017.2。這些做完,其實差不多就做完了。
7、BatchRenderAsset
我們為什麼要去做Instancing?做這個就是為了做BatchRenderAsset,用於大規Instancing渲染,比如森林、草地、城市建築等,用來取代地形的植被系統。目標就是最小化的rendreloop開銷,支持culling、LOD和streaming。所有的這些跟渲染相關的行為都是會由用戶提供的material來決定,你可以使用筆刷工具在場景的任意表面上刷Instance。然後最快今年下半年確定roadmap。要先確定roadmap,有了它以後,會第一時間跟大家分享。之後才會做,這是一點。
8、SpeedTree
另外一個SpeedTree,不會做新的feature。我自己不會做,但是其他的比如GI team會幫著做。所以在2017.1的下一個版本會做,暫時只有靜態的烘焙。還有地形上靜態陰影的一個烘焙。
9、Terrain
terrain本身不好的就是還沒有關於地形系統的計劃,中間變了很多,但現在暫時沒有計劃。好的一點是我們新招了一個很厲害的娜姐,她原來是在Bungie裡面做引擎架構,她沒有加入前就已經影響我們很多。她可能會建一個新的團隊,會重新安排一下地形系統,她也會看一下SpeedTree。
今天就是這樣,謝謝大家!