網易遊戲周潛:極速光影——探索賽車遊戲的光照

語言: CN / TW / HK

2022N.GAME網易遊戲開發者峰會於「4月18日-4月21日」舉辦,本屆峰會圍繞全新主題“未來已來 The Future Is Now”,共設定創意趨勢場、技術驅動場、藝術打磨場以及價值探索場四個場次,邀請了20位海內外重磅嘉賓共享行業研發經驗、前沿研究成果和未來發展趨勢。

今天的乾貨來自技術驅動場的嘉賓周潛,他是網易遊戲大話事業部高階技術經理。

以下是嘉賓分享實錄:(部分刪減與調整)

大家好,我是來自網易遊戲大話事業部的技術專家周潛,今天邀請大家跟我一起走進極速光影的世界,來把我們在高品質賽車遊戲的一個光照方案分享給大家。

首先來介紹一下我們的遊戲產品,名字叫Racing Master,中文名“巔峰極速”。我們的目標是去打造一個高品質、高擬真、高畫質的一個賽車遊戲。

遊戲中有非常豐富的場景以及鮮豔的色彩,賽車的物理是完全寫實的,漂移也是用真實的物理去計算的。

玩家可以對車輛進行非常豐富的自定義,包括改裝、塗裝等等。在大廳介面有車輛展示系統,車輛的建模是非常精緻的,並且車燈、車漆這些內容都是還原度非常高的,在夜景下也有十分寫實的全域性光照。

通過以上介紹,相信大家對我們遊戲的美術品質有了一個大概的概念。為了達到這樣的美術品質,我們面臨的最大一個難點,就是“實時全域性光照”。

實時全域性光照分為兩個部分:直接光照和間接光照。為了在移動端實現這一目標,我們需要面臨非常多的挑戰,包括:寬頻、效能、以及相容性等等。此外,還有著許多束縛,比如說我們需要用Forward管線來減少寬頻,畢竟我們還要減少DP,以及減少Pass等等。

那麼,直接光照和間接光照在遊戲裡延伸出來兩個需要解決的問題,就是“實時多光源”和“實時環境捕捉”,我們今天的話題就從這兩個問題開始。

一、實時多光源

“多光源”在遊戲裡是非常常見的,比如在夜景下,會有許多路燈、車燈以及車輛的回火等等,它們會照亮周圍的物體。

這個是我們賽車在經過一排路燈下的表現,除了路燈之外,車輛還有一個前置燈。這麼多的動態光源在Forward管線下是難以實現的。

因此,許多人對Forward管線進行了改進,以此來支援多光源。

首先是螢幕空間的Tile Shading,又稱為Forward+,它的思路是把螢幕空間劃分為多個格子,然後每個格子配合深度Buffer去進行燈光求交計算。這樣我們就能知道每一個格子裡會有哪些燈光對其有影響,從而減少每個畫素需要計算的燈光數量。

它雖然能夠解決多光源的問題,但需要一個預繪製深度的Buffer,即一個PreZ Pass。但如果當遊戲場景非常豐富時,PreZ Pass便會帶來非常多的Draw Call,這對於我們來說是不能接受的。

此外,Tile Shading的求交計算必須得在Compute Shader裡面進行,可對於許多手機而言,關於Compute Shader的支援效果並不友好。

另外還有一種方案是Clustered Shading,它的思路是在Forward+的基礎之上,對深度空間進一步地劃分,將視錐體劃分為多個視錐體分塊,之後再對每個分塊進行燈光求交計算。

但同樣的,Clustered Shading也需要一個PreZ Pass,並且它的求交計算也需要放在Compute Shader當中去進行。

為了在移動端去解決這些問題,我們提出了一種新的多光源方案,叫做“Grid Shading”。

Grid Shading的思路是在世界空間上,沿著XY軸方向進行齊軸的對齊網格劃分;然後採用一張燈光索引圖,其中每個畫素代表一個格子且包含該格子內所受到的光源編號。

每個畫素用RGBA四個通道,可以記錄四盞燈光。若燈光數目超出了四盞,則需要對這些燈光進行貢獻度排序。

貢獻度,即燈光的光照強度。在排序之後只保留貢獻度最大的四盞燈光於這個畫素當中。而燈光資訊則通過UniformBuffer上傳。

此外,我們可以在Z軸方向進行多層擴充套件,這樣就可以達到覆蓋範圍更廣的目的。

通過Grid Shading在場景中的應用可以看到,場景裡產生了一個XY方向上64×64的網格,並且我們在攝像機水平高度方向上,向上向下各擴充套件兩層。因此產生的總網格大小是64x64x4。

但由於網格是扁平形狀的,所以它只能覆蓋較為水平的範圍,並且使用這種方案時,玩家的視野範圍也必須受到限制,得在水平方向上的視野。

可在賽車遊戲裡,賽道基本是平鋪的,且玩家的視野基本處於水平方向,所以需要照亮的場景物體,包括賽車、路面等等都正好在覆蓋範圍之內。因而,產生的網格基本上能滿足我們的需求。

Grid Shading的流程如下。

首先需要對燈光進行遍歷,根據燈光的包圍盒計算出格子的範圍,然後在CPU層對每個格子進行求交計算。

接著計算出光照貢獻度並進行排序,將結果填充到燈光索引圖當中,並上傳燈光資訊。

最後,在繪製階段,每個畫素根據在GPU世界座標的位置,計算出它所在的格子。並且在燈光索引圖中,查找出它所需要計算的燈光編號,計算燈光的光照。

在這當中最難的一個點是格子的求交計算。每個格子的形狀是立方體,可以把它近似成一個包圍球。如果對方是點光源,則燈光範圍恰好也是球體,球體與球體之間的求交計算非常簡單。

但如果對方是聚光燈,聚光燈的範圍是圓錐體,那麼該如何做圓錐體與球體的求交計算呢?通常情況下,我們一般是提取出圓錐的包圍球,然後將該包圍球與格子的包圍球進行求交計算。

雖然這種比較簡單,但結果非常不精確,因為圓錐包圍球的大小與圓錐本身的大小差異非常大。為此,就需要一種更精確計算圓錐和包圍球的方法。思路如下。

首先,將圓錐的頂點置於一個大球的球心,則圓錐體的範圍可以看作是該大球的一個球面角內。然後根據大球和格子包圍球的位置關係,可以將求交情況劃分為4種。

第一種情況非常簡單,即格子包圍球包含了圓錐體頂點,根據這種情況可以很清楚地看到包圍球與圓錐體相交。

第二種情況是包圍球與圓錐體大球相互分離,那麼根據這種情況,則可以看到格子包圍球與圓錐不可能相交。

剩下兩種情況較為複雜。第三種情況是,格子包圍球有不超過一半的體積在大球內部。那麼此時就需要將相交的部分轉化成一個球面角,接著用該球面角與圓錐體的球面角進行相交判斷。

而第四種情況是,格子包圍球有過半的體積在大球內部。那麼我們可以用大球的球心,與格子包圍球球面所構成的切線方向,來形成一個球面角。然後用該球面角與圓錐體的球面角進行相交判斷。

通過以上四種情況的討論,便可以很精確地判斷出聚光燈和格子的求交情況了。

接下來,是Grid Shading在實際場景當中的應用。

右邊這張圖,是在比賽場景裡放置的一個路燈,以及車輛本身的前置燈二者所構成的光照情況;左邊這張圖是關於該場景的燈光索引圖。

怎麼看這張索引圖呢?通過觀察發現,燈光索引圖從上往下分為4層,對應到網格當中便是4層不同高度的網格。

另外,每個畫素是一個格子。

如果,該畫素裡有顏色,則代表這個格子受到了燈光影響。從索引圖中可看到,出現的藍色區域是聚光燈所覆蓋到的範圍。該範圍從上往下是逐漸增大的,那麼也就對應了聚光燈上小下大的圓錐體形狀。證明索引圖的結果與場景完全對應。

因此,我們在實際繪製時,就可以取樣這張索引圖來判斷畫素所對應的燈光到底是哪些了。

最後,來比較一下Grid Shading和另外兩種方案的區別。

首先在PreZ Pass階段,對於Grid Shading而言是完全不需要該階段的,而另外兩種方案對該階段則無法避免。這能夠為我們節省很多Draw Call和Pass。

在求交計算方面,Grid Shading完全可以放在CPU層面去進行,並且計算過程非常簡單,所得到的圓錐體相交結果也非常精確。但另外兩種方案不僅無法放在CPU層進行計算,且計算過程比較複雜。在面對聚光燈時,計算結果也不夠精確。

從劃分顆粒度方面來看,Grid Shading是一個能夠劃分得非常精細的方案,Clustered Shading由於在Z軸上有更進一步的劃分,因此相對來說也比較精細。但Tile Shading它是一個螢幕劃分,所以劃分的顆粒度非常粗。

所以可看到在以上幾個方向上,Grid Shading非常有優勢,並且在移動端上它的效能非常可以接受。就算在終端機上,求交計算過程中的開銷也不超過1ms。

Grid Shading的問題在於,它需要一個平鋪的水平視野範圍。但對於賽車遊戲而言,玩家的視野範圍也正好是平鋪的且處於水平方向。所以,這個限制對於我們來說並沒有太大影響。

因此,Grid Shading可以說是一個非常適用於賽車遊戲的多光源方案。

二、實時環境捕捉

首先來看一個效果演示。

在演示場景中有非常多的高亮物體,比如煙花。那麼可以看到,在夜晚或者燈光比較暗的場景下,這種高亮物體對場景的影響甚至比直接光照所帶來的影響更大一點。

反應在演示中就是,賽車和路面是可以被煙花這種物體所照亮的,而且賽車同時還會受到路面以及周圍物體反彈的間接光影響。那麼為了達成這種環境的光照效果,最重要的一步就是實時環境捕捉。

在移動端通常採用的是一種叫做“雙拋物面對映”的方案。

它的思路是,將360°的環境通過兩個拋物面對映到上和下兩個方向上,通過兩張圖來表達整個場景的資訊。

右邊這張圖就是我們在遊戲裡面捕捉到的兩張雙拋物面貼圖。賽車和賽道都必須要去取樣這兩張貼圖來獲取環境資訊。

為什麼要採用這種雙拋物面的對映方案呢?以下是我們對於幾種不同環境捕捉方案的比較,相信通過比較便能得出結果。

一般來說全場景捕捉有三種方案,分別是“球面對映”、“立方體對映”和“雙拋物面對映”。

在渲染目標數方面看,三者渲染的目標分別是1張、6張、2張。渲染數目越多代表需要更多的Pass以及Draw Call。

在畸變層面來說,球面對映會有一個非常大的畸變,且越是在邊緣處畸變越大;立方體對映完全不存在畸變,雙拋物面對映雖然也有畸變,但比較小,是可以接受的。

從對映質量來說,球面對映在邊緣處的對映質量非常差,並且有奇點;立方體對映的資訊量最大,所以對映質量是最高的;而雙拋物面對映的對映質量一般,但在移動端仍可以接受。

從計算複雜度上看,我們需要對頂點做對映變換。因此球面對映的對映變換最為複雜,因為它需要用到開方運算;而立方體對映由於只是一個簡單的透視對映,所以相對簡單;同樣,雙拋物面對映的對映變換也比較簡單。

那麼,綜合來看,雙拋物面對映是非常適合用於移動端的環境捕捉方案。

在捕捉方向的選擇上,我們可以選擇前後捕捉、左右捕捉或者上下捕捉。如果選擇前後捕捉或者左右捕捉這種方案,由於場景是平鋪的,因而賽道在這種劃分方向上會出現三角面的裁剪。最後在合成環境圖時就會有裂縫,這對我們來說是不可以接受的。

如果採用上下方向去捕捉呢?雖然也有三角面的裁剪,但裁剪的位置非常遠,玩家很難注意到。最後合成出來的環境光照也非常完整。

除了對捕捉方向的選擇外,還需要對捕捉點的位置進行選擇。

首先來看一張圖,左邊這個是溼滑路面的反射效果。大家可以看到這個路面反射有什麼問題嗎?很容易注意到的是,路面反射與實際場景的位置是不對應的。再來看這張圖,右邊這張圖看起來就好多了。兩張圖為什麼會有這樣的區別呢?

我們將相機位置給大家展示一下,左邊這張圖我們可以看到,朝前的相機是遊戲視角相機,朝上和朝下的相機是環境捕捉相機。捕捉相機和遊戲視角相機並不在同一個位置上,這就導致了畫面中位置不對齊的現象。

我們可以看到右上角的示意圖。當我們的捕捉點與相機在垂直方向上不一致時,它們在對於用一個反射方向,所捕捉到資訊在橫向上是有差異的。如果捕捉點與相機在同一個垂直方向上,那麼捕捉到的資訊只會在縱向上有一定差異,但在橫向上是對齊的。

也可以看到左邊這張圖,雖然在縱向上有差異,但玩家很難注意到。可如果在橫向上有差異,玩家就會非常容易觀察到這個現象。

不過,這又帶來一個新的問題,如果想要保證車輛的反射正確,就必須將捕捉相機的位置放在車的附近。但在遊戲中,遊戲視角相機與車輛本身的位置是有一定差異的。

所以我們沒辦法保證地面反射與車輛反射處於同時精準的狀態,二者只能選其一。可是對於玩家而言,很難注意到車輛反射的不準確性,而是更容易注意到地面反射的不準確性。

因此我們會將捕捉相機與遊戲視角相機放在同一位置,這樣來確保地面反射的準確性。

在捕捉完場景之後,需要在IBL裡面取樣這兩個捕捉內容來生成環境貼圖。但IBL有一個要求,即在粗糙度比較高的情況下,它需要對環境貼圖進行濾波。

原本我們直接對雙拋物面的捕捉結果進行動態生成Mipmap,來近似這個濾波之後的環境貼圖。但這樣會帶來一個問題,如下圖。

圖中是一個帶有粗糙度的球體,它的上下半球之間有明顯的分界線,這是怎麼回事呢?

這是因為在捕捉時,朝上的半球受到的光照強度比較高,朝下的半球光照比較弱。在濾波時Mipmap只能對一張貼圖進行Mipmap,沒辦法對整個環境進行混合。因此就帶來了上下半球亮度不統一的現象。

怎樣解決這個問題呢?我們又回想到了球面對映方案。因為球面對映是一整張貼圖,所以對它生成Mipmap時,可以對全場進行濾波。於是,可以把雙拋物面捕捉到的結果通過球面對映合成到一張貼圖上。

此外,為了減少紋理的繫結及取樣,還可以把雙拋物面的兩個捕捉內容分別放在同一個紋理的不同Mip上,這樣就能減少一部分開銷。

接下來,我們來看一下雙拋物面捕捉的流程。

首先,為了減少Pass和Draw Call,會把上下半球的捕捉分成兩個階段進行。一幀捕捉上半球,一幀捕捉下半球,兩幀交替進行。這樣每一幀只需取樣一個半球便可。

取樣到環境之後,再把它用球面對映的方式合成到一張貼圖上,接著再生成Mipmap。最後繪製階段,把它應用到場景畫素的繪製中。

那麼,在室外場景下看,這樣的表現是非常好的。但是,當我們把車開進隧道之後,又出現了一個新的問題。

如右邊這張圖,這是一輛白色的918,但在進入隧道後,它就變成一輛黑色的車了。這是為什麼呢?原因是我們在隧道中捕捉的場景非常暗,它缺少靜態光資訊。

這時,就需要去獲取靜態光資訊。那靜態光資訊到底存放在哪裡呢?它存在於我們的光照探針裡,所以就需要從光照探針裡獲取更多的資訊來進行渲染。

一般來說,間接光分為Diffuse和Specular兩個部分。Diffuse是在遊戲裡通過求些係數的光照探針來表示的。它是一個預烘焙後的包含靜態光的資訊。而Specular則是實時捕捉的內容。

在粗糙度變大的時候,需要把Specular向Diffuse的輻照度去靠攏。那麼如何實現呢?UE4其實已經做了類似的流程,但那只是針對靜態的一個方案。我們將這套流程進行了改進來適用於動態捕捉。

計算分為兩階段。首先在Vertex階段,需要對這個球面對映的內容採取它最高階的Mips,並且將這個畫素的內容點乘(0.3,0.59,0.11)。這是RGB各個通道的亮度權重值。

通過上面就得可以到環境的平均亮度。在Pixel階段,用Diffuse輻照度除以平均亮度,並用粗糙度在1.0到剛剛計算出來的這個數值之間做插值。然後將這個插值乘以Specular,這樣就能讓Specular的亮度進行提高,達到跟環境一致的效果。

那麼這樣看來,在隧道中汽車原本的顏色也能回來了,並且它的效果也跟周圍的環境比較統一。這就是全域性光照的一個表現。

在講完了實時環境捕捉以及實時多光源後,把這兩者結合起來,看看在遊戲裡的實際效果。

三、總結 

最後,我們來做一個技術的展望。

我們這套方案可以用於實時環境光照變化比較劇烈的場景,對於光源高頻變化具有較好的適應性。除了賽車遊戲之外,還可以用於不同遊戲型別,比如說MMORPG、FPS等等。

未來,我們還將會把這套方案延展到大世界以及晝夜變換和天氣系統中。另外還計劃玩家自定義賽道,這就意味著需要去實時捕捉環境間接光Diffuse的光照,這也是我們目前正在研究的一個方向。

這就是我演講的全部內容,謝謝大家!

N.GAME是由網易互娛學習發展舉辦的一年一度行業交流盛事,至今已成功舉辦七屆。本屆主題為“未來已來The Future is Now”,邀請了20位海內外重磅嘉賓、高校學者匯聚一堂,共享行業研發經驗、前沿研究成果和未來發展趨勢。

點選“閱讀原文”檢視更多分享!

https://game.academy.163.com/event/nGame

「其他文章」