自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例

Razor Yhang發表於2020-11-03
一. Intro

在PBR渲染中,除了已被大家深入分析了很多遍的PBR材質屬性(Surface Appearance)外,合理的光源強度和後處理也是不可或缺的部分。這裡結合工作中的一些實踐經驗,討論一下後處理中另一個關鍵環節——自動曝光在移動平臺上的實現。

本文討論一種不取樣backbuffer進行自動曝光的方法,以及在專案實踐過程中遇到的問題和坑,希望能對遇到此問題的各位產生一些幫助和提示。

二.自動曝光方案及其在移動平臺中遇到的問題

2.1 自動曝光是個啥,為什麼要上這個特性?

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖1 ]

自動曝光是在不改變光源強度本身的前提下,根據當前鏡頭拍攝到的場景光照強度,自動調整鏡頭的曝光值以及其他引數,使得成像結果不至於過爆(Overexposed,圖1右)或者過黑(Underexposed,圖1左)的一種操作。

這一過程就隱藏在日常生活中:拿出一部手機開啟拍照功能,對著晴朗的天空拍一張。之後,迅速將鏡頭對準屋內一個昏暗的角落,你會發現螢幕中的影像先是一片漆黑,過了一兩秒以後逐漸變亮。此時再拍照,會發現照片的亮度和對著外面照的亮度並不會差很多,至少不會像剛才那樣一片漆黑。手機這種自動適應拍照環境並調整曝光強度(可能還有其他引數)讓畫面的亮度看起來“合適”的過程就是自動曝光。

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖2 ]

很多非移動平臺的3A作品為了模擬真實的光照和人眼適應強光的效果,都會加入自動曝光功能。FPS品類的例如Call of Duty,Division;競速類的例如近幾代的極品飛車和Forza Horizon;ACT類的刺客信條、鬼泣等都有非常明顯的自動曝光feature。以上圖中這個 Division 2 的場景為例子(https://www.youtube.com/watch?v=EMkZ7sOoRSw, 00:50到01:00)。角色相機在進入一個相對較黑的屋內之前,看屋裡幾乎無法分辨室內的任何東西。走到屋內以後,自動曝光功能將鏡頭的曝光值提起來了,使得玩家可以看清屋內的物體。同樣的光照環境,變的只有相機屬性,卻會對場景物體的辨識度產生很大影響。這是自動曝光作用的一個典型案例。

不加證明地總結自動曝光特性的一些好處:

  • 允許場景的光源對比度更大,動態程度更高,從而提高光強的真實度
  • 提高場景中暗部的辨識度,這一點對於某些遊戲品類很重要
  • 可以間接的減少美術同學的“補間接光”的操作

可見,若希望在移動平臺上將真實感渲染品質往端遊和主機的品質push一下的話,產品渲染管線中的自動曝光功能大概率是不可缺少的。尤其是筆者接觸較多的FPS品類,它們的場景漫遊攝像機會在光照環境變化劇烈的室內外頻繁切換,這時自動曝光的作用就顯得格外突出。

2.2當前技術覆盤

回到遊戲渲染中的技術來,一起看看現在工程中的做法。以最常見的Unreal 4與 Unity來說,他們的自動曝光處理流程可以大體概括為:

  • 取當前幀的影像到一個renderTexture(RT),獲得當前幀場景的光照資訊。
  • 一般,還需要對當前幀進行降取樣(縮小畫面),節省分析時候的效能開銷。這一步可以與後處理管線中的其他處理步驟合併。
  • 通過各種方法,例如多點取樣、compute shader等手段,根據降取樣buffer中的資料,得到這一幀畫面中場景的光照資訊。
  • 根據這些資訊,以及各家的演算法,做自動曝光的處理。

處理曝光部分,除了最基本的將曝光值提高或者降低以外(相當於最後輸出時候將光強乘了一個係數),還有可能結合其他圖形處理技術對畫面進行優化,例如UE4會分析當前幀的直方圖,然後做一些類似直方圖均衡的操作。有關Unreal 4 與 Unity的自動曝光的一些分析,可以參考這篇文章:https://zhuanlan.zhihu.com/p/76416912。

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖3 ]

上述操作基本上都是針對單一一幀內容進行的操作。除了靜態的處理,自動曝光還需要考慮動態的一些東西。例如從暗到亮或者反過來的速度,以模擬眼睛的明適應和暗適應。在圖3中,SpeedUp / Down 控制的就是明暗變化的速度。

2.3 技術痛點與期冀

根據上述的覆盤,我們發現當前的自動曝光方案均需要讀取當前渲染環境的螢幕空間亮度才能繼續後續的運算。意味著需要至少 swap 一次 RT 才能完成這一操作(如果有不佔頻寬的方法求不吝賜教),這對於2019-2020的中低端機來說還是太費了。以2019年下半年的標準,像 iphone6 / oppo r11 這類的中配機一般僅允許有一次RT的切換,個人是感覺這一次切換不如給shadowmap來的值得。而對於像 oppo A33,米4、榮耀暢玩 5X 這些2019年的低配機型來說,連保證全場景PBR輸入的頻寬都困難,更不要說什麼切RT了。因此,我們認為傳統方案中需要讀backbuffer到RT這種方案,天生會成為中低配機的痛點。

基於這種分析,結合筆者當時專案的特點,我們給出本文中自動曝光方案的要求和期望:

  • 不能用額外RT,不能來回切RT
  • 更不太可能碰compute shader
  • 能夠滿足最基本的自動曝光功能,即根據攝像機可視區域的平均亮度,將輸出到tonemapping的亮度乘一個係數使他保持一個相對合理的亮度。
  • 高中低配使用同一套方案,不搞高配一套低配一套。不能因為機型問題產生fallback、美術高低配不同的工作量以及因此帶來的大小包問題。這是專案本身的要求和屬性及以往專案的經驗教訓決定的。

下面將根據這些要求聊一聊本文的自動曝光方案。

三.方案詳解

3.1落地效果

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖4 ]

這一方案去年下半年的時候就已經合進筆者參與的 FPS 專案 《使命召喚手遊》(Call of Duty : Mobile ,CODM)裡了,不過當時並沒有經過玩家和市場驗證,並不敢冒然分享可能不靠譜的東西。目前,該專案已經有若干張圖使用了該方法,已有外國玩家錄製了地圖視訊,反響還不錯(https://www.youtube.com/watch?v=OE1-BeUudGs)。

如圖4所示,玩家從光照較弱的洞內走向光照充足的洞外,自動曝光自動將畫面亮度壓了下來使得場景的亮度一直保持在一個相對合理的亮度。自動曝光的效果通過視訊展示更為明顯,推薦各位通過玩家錄製的遊玩視訊或者親自下載應用體驗效果~

3.2思路

上一節分析中我們提到,現有方案的關鍵痛點是取螢幕空間亮度這個操作在中低配手機上開銷太大。因此,我們需要從這裡入手另闢蹊徑。實際上,只要獲得攝像機當前視角下,觀察到的物體平均亮度,就可以繼續傳統自動曝光處理的剩餘工作。因此,我們需要一種非常廉價的方法,替代當前這種使用 rendertexture 獲得平均亮度的方法。

除了螢幕空間實際看到的畫素點以外,還有什麼方法可以獲得場景某一處的亮度呢?很幸運,我們在 CODM 現有的渲染管線中找到了一個非常合適的候選人——為動態物體提供間接光照的 Lightprobe。Unity 的 Lightprobe 是以四面體網格儲存的場景間接漫反射光照(GI)的球諧函式係數(SH),當這個 renderer 處於GI SH網格中時,renderer 可以通過取樣 SH 四邊形網格並通過插值獲得 renderer 當前位置的間接光SH值。思考這個SH的值可以發現,若我們在場景中的某一個位置對一個方向取樣 SH,得到的數值就是這一點取樣方向上半球內場景亮度的 cosine 積分。這與我們想要的場景某一位置看到的平均亮度有非常強的相關性。同時,採 SH 網格開銷一般是遠遠小於 swap buffer 再把 rendertexture 返回到記憶體中的。因此我們認為使用lightprobe的資料是有可能實現低成本自動曝光的。

經過一些實驗和思考,我們認為這個思路是比較靠譜的。只要把採螢幕亮度這一步,替換成採場景攝像機位置視線上的SH就可以搞定了。拿到場景的SH之後的操作按照傳統自動曝光的操作進行處理即可。本文也將主要圍繞如何高效能取到鏡頭之內場景的平均亮度,而忽略取到亮度之後處理曝光值部分的討論。

3.3 將SH到場景平均亮度的轉換

一旦思路有了,驗證這個方法的手段非常簡單。但是一旦親自嘗試,就會發現直接使用SH資訊根本不穩定。我們還需要對採集SH的位置以及SH資訊本身進行一定的分析和計算才能得到可用的場景平均亮度。

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖5 ]

首先,一起回顧一下GI SH的數值含義。如圖5左側所示,若從某一點向一個方向取樣場景GI的 SH值,我們會得到什麼?只要對lightprobe原理以及球諧函式表達GI這套東西熟悉的話,就知道它得到的是取樣點這一方向對半球內radiance乘cosine的積分。簡單的來說,就是取樣方向半球可見區域裡面“亮度”的cosine加權平均,方向越接近取樣方向,權重越大;反之,半球內垂直於取樣方向上的radiance權重幾乎為0。若從數值上分析,我們可以發現這個方向上的SH值,大約等價於這個方向上一個球面角之內radiance的平均值。這個夾角的半形用α表示,如圖5右側藍色區域所示。我們忽略掉半球積分內佔總權重非常小的那一部分灰色區域,粗略認為SH的值大約等於這個立體角半形α的錐形之內場景radiance的平均值,即藍色範圍內場景的平均亮度。

經過不少實驗和摸索,在實際落地中我們將α取經驗值:52度。在這個度數下,藍色區域的積分值展總半球內cosine加權積分值的85%以上。有了這層等價關係,我們就可以將SH值、相機方向、相機FOV值聯絡在一起。當我們在場景中某一點向一個方向上取樣得到了一個SH,我們就認為這個SH值是一個朝著取樣方向,且FOV為104度的攝像機拍攝到影像的平均亮度。

這種等價關係引入了本方法的第一個數值偏差,即那部分灰色的區域以及藍色部分的加權問題。假設在灰色的區域內場景突然出現了一個特別亮的物體,則這一部分割槽域會把整個SH的值拉高一點,使得相機走過這一區域時候的曝光值降低(亮度平均值高於實際場景值)。經過一些測試,我們認為這個角度下是可以滿足大部分的使用情形的,問題不大。請各位讀者在實踐中留意這一部分數值偏差。

3.4 加上相機的FOV

我們已經將SH值與場景平均亮度之間建立了數值聯絡,但是這還不夠解決問題。

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖6 ]

實現過自動曝光或者有攝影經驗的朋友一定知道,無論是實時渲染的自動曝光,還是數位相機的自動測光(圖6),都不是簡單粗暴地對當前畫面內的所有區域的亮度做平均,因為畫面中心的亮度往往更重要。對於數位相機,測光的方法只會選取畫面中心區域的一個點或者幾個點進行測量,而不是對全畫幅進行數值平均。實時渲染中的自動曝光演算法也有類似的操作。

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖7 ]

以圖7為例子,若要對上圖中的場景進行自動曝光處理,則需要統計下圖中中心區域的亮度。基於此,我們可以得出一個結論,自動曝光需要統計的場景亮度立體角總是會小於攝像機的FOV。也因此,我們會遇到兩個比較嚴重的問題:

  • 第一,在上一節的等價關係中,我們認為GI SH的值約等於FOV 104度的攝像機的亮度平均值。而遊戲中的相機FOV幾乎不可能總是104度。一般的FPS FOV在30度到90度之間,還需要考慮開瞄準鏡時,FOV急劇變化的問題。
  • 第二,假如相機的 FOV確實是104度,我們實際用來測光的立體角肯定比這個值要小。如果直接使用GI SH的值,統計出來的範圍比相機實際看到的範圍要大。

這兩個問題說明,實際落地時不能像大多數讀者想的那樣直接在拿相機的位置和方向去採SH就完了。前期研發時的測試也證明,不做任何處理直接拿出來的SH有比較大的誤差,尤其是在視窗、門口這種內外亮度較大的位置上,這些位置的亮度幾乎一定是錯的。

怎麼辦呢?筆者認為可以通過修改SH的取樣位置來修正這兩個問題。本文中的方法,將SH的取樣點在相機的視線向量方向上向前移動一段距離,使得SH的取樣資料能夠基本吻合常見的FOV值。

自動曝光在移動平臺上的實現方案——以《使命召喚手遊》為例
[ 圖8 ]

我們一起來看一下怎麼移動取樣點。圖8引入一個新的半形θ表示相機用來統計螢幕亮度所用的FOV,注意這個值一般都比相機本身的FOV要小。我們不妨認為,相機需要統計的螢幕亮度值全部由圖8左側的灰色虛擬物體提供,攝像機看到的這個物體的亮度就是自動曝光想要的值。那麼,物體的亮度怎麼獲得呢?只要讓SH等價的那個104度立體角的錐切著物體的兩側正好包住物體就好了,如圖8藍色線區域所示。我們將SH取樣點A往灰色物體的方向上移動,使得藍色線正好蓋住物體。在這種情況下,根據剛才的約等於關係,我們可以認為A點取樣的SH值,大約等於灰色物體的平均亮度,即自動曝光平均亮度。

移動的這段距離,實際上就是線段AO的長度。倘若我們知道虛擬物體到攝像機的距離,又知道α的角度,便可通過簡單運算得出AO與CA的距離,在此不表。

引入灰色虛擬物體的這種假設,在落地的時候會遇到一些必須要處理的坑:

如何拿到CO之間的距離?

這一資料是保證本方案可行性的關鍵資料。筆者的專案裡因為場景具有相對高效的碰撞盒,因此拿到場景大致的外包盒並不會太難也不會影響效率。同時,這個取樣也不用每幀都做,因此效能上還好。如果專案因為某種原因拿不到CO的距離,使用一個固定值也能達到一定的效果。但是諸如穿牆、距離過近的情況會有問題。看自己的專案情況吧。

CO的距離無限大怎麼辦?

在筆者的專案中,除非是開了狙擊鏡,否則會強制要求AO的距離控制在0-8個場景單位之間。基於上文的假設以及專案資料,在這種假設下CO的距離可以達到20個單位左右,完全可以滿足專案要求。同時我們也調整過AO最遠允許的距離,發現調大以後並沒有什麼卵用。因此為了保證求交結果的穩定,我們將AO的值限制在了8以內。當然,這個數值可能需要根據專案的情況進行調整。

灰色物體離攝像機太近怎麼辦?

數值上來說確實無解,不過在實際測試過程中並沒有產生太大的問題。

灰色物體的法線如果不正對這攝像機怎麼辦?(站在地上看地面)

實際落地中並沒有對此做過多的處理。倘若是地面的話,我們會將測量點沿著地面法線方向向上抬高一點點來補償這個問題,但是這個操作的收效甚微,幾乎可以忽略。

3.5 落地

基於上面幾小節討論的內容,總結一下這種自動曝光的方法步驟:

  • 根據相機的FOV、位置、方向,確定採集GI SH的大概位置。
  • 根據3.3節中的偏移,確定取樣SH的精確位置。
  • 取樣GI SH,得到當前時刻可視範圍內的場景亮度。注意,這個操作不是每幀做的,大概一秒5-10幀足矣。
  • 根據亮度確定曝光值是該提高還是降低,確定一個目標曝光值。
  • 根據一些與時間相關的引數,將曝光值向目標曝光值調整。
  • 將曝光值傳給後處理鏈,得到最終的曝光效果。

在落地過程中還需要注意一些事情:

注意場景 GI SH的有效範圍

一般來說為了節省資源和記憶體,我們只會在攝像機可能經過的區域佈置SH網格。自動曝光的加入無疑擴大了這個網格的範圍。也因為這個原因,這個方案更適合一些封閉的小場景。對於時下流行的開放大世界,則必須藉助非常規的 lightprobe 結構實現自動曝光。對於此要麼增加SH網格的範圍,或者可以考慮像筆者一樣使用一些hack。例如站在地面向天上看的情況,一般情況SH網格並不會給天空方向很多的資源,這就導致採集到的SH並不能代表角色頭頂上方的SH值。遇到這種情況,筆者是將SH取樣值的位置強制限制在相機上方一定高度之內。事實證明這種方法很有效。另外還有一種情況就是從室內看窗外,如果窗外的SH不能代表窗外的亮度,則曝光就會錯誤。然而如果是不能走到的視窗,外面一般是沒有SH網格的,這就產生了錯誤。筆者建議這部分使用一些自動化工具來檢查,靠人檢查是不太行的。

GI SH一定要能代表場景的實際亮度

這是筆者在實踐中遇到的最大問題!Lightprobe可能會因為各種原因無法表達場景的實際亮度,本文中的自動曝光方法遇到這種情況就會錯誤的壓低曝光值。這一點就需要美術同事一起努力避免。想要完全避免這部分問題,最好的方法還是完全設定一套獨立的SH網格來做自動曝光,當然也需要更多工作量。

四.未來的工作

(1)未來能夠改進的點

雖然當下的方法已經上線了,但是不免還是有一些遺憾。目前覺得最想繼續提高的,就是在這個思路下支援動態物體。可以考慮將能產生強烈影響光照的物體抽象成一個球體,然後再將發光強度project到SH上從而影響曝光值。

另外,就是這個方法在類 volume lightmap (UE4那套)的落地。其實只要能搞到與場景亮度一致的GI SH,這套方法都可以用。不過取 VLM 的效率有可能不如取 lightprobe 高,同時還有可能有精度上的問題。

(2)有關自動曝光生產的一些經驗

實現自動曝光的技術特性只是光照強度更加PBR化的一小步。回到遊戲產品的生產過程中,筆者結合產品經驗以及與團隊中美術老闆們的討論,在這裡給出一些與自動曝光對專案產生的影響,這些東西只是實踐產生的一些經驗和忠告。當然,它們可能並不適合所有的專案:

  • 自動曝光功能會改變場景中光源在布光中的作用,會非常強烈的影響場景美術以及燈光師的場景打光思路和流程。例如一間暗室的視窗,有自動曝光時可以當區域性場景的主光源,沒有的話就需要美術額外補一些光做出類似視窗過爆的效果。
  • 自動曝光功能會讓場景布光強度更合理,間接地讓渲染結果更“PBR”。我們觀察到開啟自動曝光後,場景美術在布光時對比度會變得更大膽,不像原來那麼“平”。這更符合真實光照環境中的情況。
  • 美術同學要適應帶有自動曝光特性的場景,並且根據自動曝光的“脾氣”制定一些布光策略。
  • 自動曝光必須對美術所見即所得,否則會變成場景和燈光師的噩夢。
  • 渲染管線中有沒有自動曝光,最好能早點確定好,否則會給場景生產造成混亂和返工。


來源:騰訊遊戲學院
原文:https://mp.weixin.qq.com/s/YT0xeUzEiOzhTwPHee_HBg

相關文章