VR場景優化,知易行難
開發者們或者玩家,在開發或者體驗各種PC/主機遊戲,還是手遊以至VR遊戲,都會對這樣一個名詞有著特殊的理解和關注,那就是FPS(幀速率Frames Per Second)顧名思義就是每秒鐘渲染的幀數,即顯示卡和顯示器每秒鐘都會更新數十張不同的影象(幀),進而形成一種動畫的效果。對於一些差的內容的體驗,玩家也會給與出“哦,太卡了”類似於這樣的評價,這正是FPS的原因,如果FPS太低,畫面就一卡一卡的,而最新的要求:90
幀每秒-這是對 Gear VR 消費者版的新的目標:你需要硬體能夠穩定的渲染兩塊 HD 畫面,以 90 幀每秒的重新整理率,否則畫面抖動你就會感覺很暈——戴上VR頭盔之後,採用OLED螢幕的內容則可以達到75FPS的更新,事實上,為了避免出現眩暈感,遊戲內容的畫面重新整理也必須達到75FPS。
但互動遊戲並不是預先拍攝好的電影,它需要根據玩家的操作來觸發不同的邏輯和劇情,並對應的渲染出不同的畫面。 而一個遊戲場景可能是複雜的,細碎的,並且有很高的真實感和特效方面的要求。如何優化我們的場景和渲染策略,在如此有限的時間要求內,實現高效與細節並存的遊戲內容呢? 本文將針對這一話題做適當的討論,只期望為讀者和開發者們小啟一扇門扉。
渲染批次(Draw Call)的思考,卡車花在路上的時間太長
舉個栗子:假設有一個果園,每天它都要派出一輛卡車,走固定的路線運送水果到市裡。如果運送的水果總量為10噸,而這輛卡車一次能夠承載200千克水果的話,那麼它需要跑上50趟才能完成這一任務,也許這會花費整整一天的時間,
內容開發者們同樣遇到了這種問題的時候,第一選擇往往是換用更逆天的顯示卡和系統,送200趟水果的卡車,就好比跑了200個渲染批次的顯示卡,而每個批次的執行都是會消耗固定時間的。而為了縮短這一時間與其直接買入更新的顯示裝置,倒不如先仔細想一想,渲染批次過多的瓶頸是什麼,我又能把它優化到什麼地步(至少恢復到50個批次的正常水準?),
以往製作遊戲場景的美術人員更願意用“三角面數”(Triangle Face)來描述內容的複雜程度或者製作水準,但對於實時渲染而言,三角面數並不足以決定渲染的效率以及幀速率結果。僅用了一個批次就完成渲染的1000萬面模型,和用了1萬個批次才渲染完成的100萬面模型,其執行效率恐怕是天壤之別。後者在實際執行當中的表現,恐怕一定會讓那些自信於低面數模型的人們大跌眼鏡。
為什麼會有大量的Draw Call呢?
但為什麼不一開始就設計成一次Draw Call執行全部場景物體的渲染操作呢?就算是因為承載力的問題不得不分成多個Draw Call,這種簡單粗暴的設計依然應當最能解決眼下問題吧?然而這樣的願望往往無力成為現實。
因為現代圖形渲染底層介面對於Draw Call的實際執行,是在繪製實際幾何體圖元(Draw Primitive)的階段;而每次繪製圖元的操作之前,我們只能為這組圖元(可能是200個三角面,也可能是10萬個三角面)設定一張紋理影象(Texture),以及一組著色器(Shader),而這兩者正是虛擬現實和遊戲應用當中用於表達物體材質和真實感的最核心元件。
一張影象能夠容納和清晰地表達一個精緻美女的肌膚,秀髮,慧眼,紅脣,高跟鞋,LV包,以及其他種種細緻入微的內容嗎?當然不能。所以我們也沒有辦法在一個渲染批次裡搞定美女模型的一切。
而與此相關的另一個問題也會困擾著更深層次的圖形開發者:場景內容的管理。例如一座數字化的虛擬城市,每一棟樓、房間、居民,都應當是獨立存在的個體,對這些模型資源的管理也顯然應當按照相似的分類方法,然而從渲染的角度來說,這樣產生的渲染批次恐怕遠遠不是最優的選擇,甚至輕而易舉就會讓前文中送水果的卡車司機崩潰掉。
如果按照材質把模型重新分組和整合,倒是可以進一步提升渲染的效率,不過資源的管理工作卻無疑會讓人崩潰。
有關填充率
現代計算機的圖形渲染過程與此類同。把複雜的場景模型躍然於螢幕之上,這一過程稱作光柵化(Rasterization),而螢幕上的畫素點,近看起來同樣存在顆粒感,低解析度的螢幕則更為明顯。這種顆粒感對於VR類的內容來說更為顯著(因為VR眼鏡相當於放大了螢幕解析度對畫面質量的影響),而它也是破壞場景真實感和效果的主因之一。
有人可能會說,那就拼命增加螢幕解析度不就好了,從現在的1080p,到2K,到4K,到8K……總會有徹底解決這個問題的一天吧?然而事情並沒有這麼簡單,還是回過頭來談談我們的畫家:在一張A4紙上畫簡筆畫,也許他只需要1-2分鐘而已;如果是10米長卷,那麼也許要一天的時間;如果是在萬里長城上……那麼畫家可能直接就跳下去了,搞這麼一輩子的工程,生不如死啊。
沒錯,這裡的畫卷可以類比為我們所說的螢幕解析度,而畫家求死的原因,只因為要畫的東西太多,而他對畫卷內容的填充率(Fill Rate)太低了,因而渲染效率也變得慘不忍睹。
這個問題的解決方案,無非三種,弊端也是一目瞭然:
-
改用小點的畫卷(降低螢幕解析度和使用者體驗);
-
換個瘋狂的畫家(升級硬體,提升填充率);
-
少畫點花裡胡哨的東西(降低渲染內容的質量)。
聽起來都不是什麼一勞永逸的選擇,並且大多數開發者一定會選用最直接的那個方案,沒錯,換更瘋狂的畫家,搞硬體的軍備競賽。
不過從場景優化的角度來說,方案三反而成了最靠譜的一條道路,這也提出了一個有趣的命題:如何在渲染質量還看得過去的前提下,儘量少畫點“對使用者沒用”的內容呢?
對這句話的詳細解釋就是:
-
使用者看不到的場景不要畫出來,把它提前裁減掉(Culling);
-
而使用者可能本來也看不清的場景,就用更低的細節程度(Level of Details)把它畫出來。
但顯示卡並不是多麼聰明的一種硬體產品,它並不能主動分辨出當前提交的渲染指令中,包含的資訊是否真的能夠被顯示到螢幕之上;而是選擇了另一種更為簡單的策略:不管有多少東西都先畫上去,如果不幸沒畫到紙上的話……反正你也不在乎對不對?畢竟終端使用者關注的只有紙面上的內容而已。
然古今中外,那些為了優化場景而苦思冥想的開發者們,卻僅為了這一個目標前赴後繼,傷痕累累。
當然,這裡所說到的“填充率”一詞,只是相關圖形系統執行機制的冰山一角。它還可能被進一步細分為畫素填充率(光柵化操作和螢幕快取繪製的速率)和紋理填充率(紋理在模型表面對映和取樣操作的速率)。而實際執行過程中,還可能受到視訊記憶體頻寬(顯示卡在單位時間能夠傳輸資料的總量)引數的影響,而這些資訊往往都會標識在具體顯示卡品牌的效能說明文件中,作為發燒友比較和購買的依據(雖然它們實際上並無統一標準可言)。
不過這並非本文所要繼續深入闡述的命題了,我們關心的,還是那些圖形學和VR領域的先行者們,為了哪怕一點點的優化效果做出過的努力。
奇思妙想與日夜苦熬
之前的長篇大論,列出了各種看起來棘手和不可逾越的問題,在這裡歸結起來,無非有以下幾點:
-
渲染批次(Draw Call)的合併與優化問題;
-
裁減看不到的物體,降低渲染批次和填充率;
-
對於看不清的物體(比如距離玩家位置較遠的物體),改變細節程度並降低填充率。
單純合併幾何體資料也許只是一個數學和幾何拓撲學上的問題而已,然而每個渲染批次只能使用一組材質,這就大大提高了工作的難度和策略性。一個聽起來還不錯的方案就是,將不同的材質合併到一起,這樣對應的渲染批次也就合併在一起了——這一過程通常被稱為Atlas。沒錯,就是《雲圖(Cloud Atlas)》那部電影中所體現的,把不同的碎片(紋理)拼合在一起!
這一工作並不像想象中那麼簡單,因為你要合理地選擇被拼合的紋理影象,以及考慮拼合的結果是否真的就提升了你的渲染效率?過度的Atlas只能讓系統變得更慢,甚至遠低於拼合之前的結果。然後是資料的裁減(Culling),同樣是一個簡單而又複雜的命題:如何定義“玩家看不到的物體”?
最簡單的一種情形是,他視野之外的物體。也就是說,如果把人的視野當作是一個空間的錐體的話,那麼暫時丟掉這個視錐體之外的所有物體,讓它們不要被渲染出來即可,即視錐體裁減(Frustum Culling)。
另一種麻煩但是很有意義的情形是,被其它什麼東西擋住的物體。比如床底下的皮球,穿在外套下的襯衣等等。他們雖然在觀察者的視野範圍內,在當前時刻卻不可能被看到。此時我們也可以選擇直接剔除掉這樣的物體,因而降低渲染批次和填充率——不過前提是,我們能儘快先找出那些確實被遮擋住的物體來,也就是遮擋查詢(Occlusion Query)和遮擋裁減(Occlusion Culling)的概念。
不幸的是,在空間中進行復雜形體之間的遮擋判斷,絕非易事。就算有合理的數學方法可以最終遍歷和找到所有被遮擋的物體,這一運算過程耗費的時間恐怕也早已超過了直接渲染它們所花費的時間。因此,更為簡化和高效的裁減演算法研究也成為了一個仍在持續進行的話題,近年來更有了不起的開發者群體實現了自己的軟體光柵化過程,僅用作快速的遮擋判斷。不過相關技術的普適性驗證和推廣,還有很長的一段路要走。
另一種同樣重要的優化方式就是場景物體的細節層次(Level of Details,LOD)劃分。
當物體距離觀察者較遠的時候,採用粗糙層次的物體模型;靠近觀察者之後,採用精細層次的物體模型。這種策略對於大規模場景的渲染(尤其是那些動輒以數十數百GB來算的地球級場景資料),往往有著不可替代的價值和意義。
當然,單純的離散模型LOD也有種種的弊端,譬如切換層次時的突兀感,以及資源管理的複雜性等等。人們也在不斷研究更新的分級,自動處理,分頁排程等策略,只為了讓更多更好的內容躍然於那不斷增幅的畫紙之上。
而高質量的渲染畫面,對於場景優化的負面影響,往往也是不可忽略的。比如遊戲中最為常見的陰影圖技法(ShadowMap),從光源的角度重新渲染一次非黑即白的場景,再對映到原始的場景畫面當中,形成逼真的陰影效果。這個看起來必不可少的需求卻讓我們苦心降下來的Draw Call直接翻倍。又比如多重取樣抗鋸齒(MSAA),實時反射(Reflecting)等種種效果需求,都是快速吃掉有限渲染效率的大殺器……為此,已是熬成謝頂與白髮的開發者們也只能重整旗鼓,在場景優化的狹小空間裡再耕耘,再奮戰;而這一切,也許從未被螢幕前的玩家所知吧。
屬於VR的戰鬥,高質量的內容如何去呈現?
VR眼鏡本身具有左右眼分別渲染的特點,因此這也意味著實際場景也要根據左右眼的位置和視角分別渲染一次。這就意味著渲染批次的直接翻倍,填充率指標也大受影響。一些原本在PC顯示器上流暢執行的逼真遊戲,一下子跌入“卡頓”的谷底,而這還遠遠沒有盡頭,VR行業的研究者們已經證明,90Hz乃至更高的重新整理率和渲染幀速率才是人們的期望。
翻倍的渲染壓力,以及近乎翻倍的幀速率要求……現在這些低劣的VR遊戲場景騙不了最終的消費者,各種幼稚和簡單的遊戲邏輯也絲毫無助於VR元年的期望。
AAA級別的大作在哪裡?黑客帝國一般的幻境在何處?要為此付出巨大努力的絕不只有硬體工程師們,也絕不可以簡單歸罪於美術人員的無能。 要知道,縱使是Unity和Unreal這樣的成熟商業引擎,面對VR的苛刻需求和毫無優化可言的套用的傳統遊戲場景,其渲染效能也會捉襟見肘,狼狽不堪——而這一切的始作俑者,正是缺乏圖形底層知識和深入實戰經驗的開發者自己;能夠想到方法,脫出困局的,也只有靠這群戰士發奮後百倍千倍的努力。是的,屬於VR的戰鬥,才剛剛開始。