技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


編者按 本文介紹了四個小主題,分別是UE4 Mobile端的Skylight和ReflectionCapture之間的關係,如何讓ReflectionCapture採集天光,ReflectionCapture的亮度校正算法分析及在期在移動端可能的優化。值得一提的是ReflectionCapture IBL的壓縮是UE4.26中已被實現。


文 | Jiff

(本文首發於知乎專欄“圖形遊戲和宅”,轉載請徵得同意。)


這篇文章是UE4 反射球系列文章的完結篇,內容包含以下幾個部分:


1. SkyLight和Reflection Capture的Cubemap有何不同?


2. Reflection Capture為何採集不到天空球Mesh?


3. Reflection Capture的亮度是如何確定的?


4. 對移動端來說,Skylight/Reflection Capture有哪些可用的優化?


SkyLight和Reflectioin Capture


SkyLight所提供的是直接光照,同時包含漫反射和高光反射,而Relfection Capture提供的是環境反射,只有間接高光且不包含漫反射。嚴格來說,這兩部分光照信息的頻度定位不同,是疊加關係,不能放一起比較。


凡事都有意外,UE4在移動端的標準光照模型中SkyLight Cubemap和Reflection Capture Cubemap是互斥的——要麼存在的是Skylight,要麼存在的是Reflection Capture,且在選擇時Relfection Capture的優先級高於Skylight。不光如此,移動端的Relfection Capture的範圍也無效。因為兩者的互斥關係移動端PBR渲染過程只需要採樣一次cubemap,相比多次採樣cubemap更廉價,能耗更低。


但同時這些做法也帶來了一些問題:


1. 場景中同時存在Reflection Capture&Skylight時,因為Reflection Capture優先級高且範圍無效,永遠只可能Reflection Capture有效。


2. 相對於非移動端來說,移動端的場景中IBL所提供的光照往往會來得更暗一些。


3. 因為Reflection Capture的範圍無效,物體在渲染時只取離它最近的那一個,也導致在場景製作過程中,需要區分室內室外,樓上樓下,多變的環境氛圍時工作流幾乎不可能實現,因為無法精確控制範圍。這個問題對於想要製作高品質遊戲場景來說,說致命並不為過。


Reflection Capture 為何採集不到天空球


在場景和TA的強烈要求下,我們在移動端修復了Reflection Capture的作用範圍,想要在移動端讓範圍起作用,只需要在FScene::FindClosestReflectionCapture裡查找和物體包圍盒相交的的Reflection Capture並處理好Mobilebasepass的ShaderBinding參數設定即可。


這個問題修復之後,TA很快又發現兩個新的問題:


1. 同樣的一樣IBL圖,當它作為Skylight輸入存在時比作為Reflection Capture輸入時對亮度的貢獻大很多。


2. 放在室外的Reflection Capture,上半部分是黑的,一查原來是採集不到Stationary的天空球。


第一個問題留到第三部分去說,第二個Reflection Capture採集不到天空球的問題,則是因為Skylight有一個選項用來控制天空球的Threshold。


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


這個選項的直接意思是:距離原點多遠之後的場景物體屬於天空球,這個默認值是1500米,即1500米以外的所有物體,都屬於天空。因為UE4在非移動端的實現中Skylight和Reflection Capture相互疊加,且當Reflection Capture和SkyLight同時可見時,優先選用的是Skylight的部分。這樣的話,在存在Skylight的室外,採集ReflectionCapture時確實不需要採集和存儲天空球。


這個設計初衷所帶來的問題在於:你要是移動端的話,你就完蛋了——你室外的光滑物體和純金屬,上半球一片黑。黑夜給了你的黑色眼睛,是你看到了自己心理的陰影?


我們來看看UE4在採集Reflection Capture時,是如何丟掉天空球信息的。


1. 實現代碼在ReflectionEnvironmentShader.usf 的CopySceneColorToCubeFaceColorPS函數中,代碼如下所示:


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


這段代碼的解釋為:噹噹前採樣到的位置距離< 0.8 * threshold時,IBL的Alpha值為1,否則小於1,按1-Smoothstep曲線(似乎看到的實現,大多數SmoothStep都是三次曲線)方式趨向於0,當距離大於等於threshold時,Alpha必然為0,Alpha 值會被寫入Cubemap的Alpha通道。


2. SkyLightParametersValue參數傳遞的入口在ReflectionEnvironmentCapture.cpp中的FCopySceneColorToCubeFacePS類的SetParameters函數中,代碼如下:


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


可以看到這兒的SkyLightParametersValue.x即為 Skylight上的SkyDistance Threshold值。


3. 在FilterReflectionEnvironment函數中一開始執行一次premultiply alpha,這時alpha值同會乘以rgb值,所以會造成最終的cubemap中alpha為0的值也變成了全黑色,代碼如下:


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


即:Color = 0 * srcColor + destAlpha * destColor


經此一步之後,不管是移動端還是非移動端,其生成的Cubemap中被判為天空的部分已經全部為0,所以在反射球為移動端存儲編碼為RGBM時,早已沒有了這部分顏色信息……


移動端的IBL亮度計算


上文說到使用同一張Cubemap作為Skylight和Reflection Capture輸入時,得到的結果亮度不一致,於是乎TA提了一個BUG單。


沒過多久,場景美術發現無論是接受CSM實時陰影或是烘焙的ShadowMask,金屬物體上的陰影總是比非金屬上的陰影來得更黑一些,於是乎場景美術不光提了一個BUG單,還抱怨說UE4怎麼這麼多亂七八糟的問題,比隔壁另一個U字頭的引擎還不如?


Skylight和Reflection Capture亮度不一樣的問題,是因為Skylight的cubemap在渲染時直接使用的是cubemap的原始亮度,而Reflection Capture在渲染時亮度經過了縮放(大部分時候,這個縮放值都是小數,也就是說:它都會變暗)。具體的實現在MobileBasePassPixelShader.usf的GetImageBasedReflectionLighting函數中。


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


可以看到在Reflection Capture的情況下,SpecularIBL會乘以縮放值。這個值是通過MobileComputeMixingWeight函數計算出來的——如果你用的UE4版本在4.23之前,那麼這個函數是不帶Mobile的ComputeMixingWeight,Mobile版本只是簡單的把ComputeMixingWeight的所有數據類型,由float改為了half*,算法完全一致。


接下來我們詳細拆一拆MobileComputeMixingWeight的算法,先看看這函數的全貌(因為源代碼中的註釋有很強的誤導性,所以註釋去被我去掉了,同時我也簡化了一下代碼的佈局)。


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


算法分為這幾步:


1. 計算一個0~1之間的MIxingAlpha值。


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


這個值由ReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight的x、y及當前像素的Roughness值來確定。Ref*Weight參數的計算過程。


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


寫成數學公式:

x = 1.0/(b-a) ,b為結束Roughness值,a為開始Roughness值

y = - a / (b-a)

把上述x,y代入到MobileComputeMixingWeight中的MixingAlpha式中得

MixingAlpha

= smoothstep(0 ,1 , Roughness/(b-a) - a/(b-a)

= smoothstep(0 , 1 , (Roughness - a)/(b-a))


可以看到MixingAlpha值和Roughness值成正比,Roughness越大,則MixingAlpha值也越大,最大值不超過1(staturate所限)。


2. 計算MixingWeight(Normalized Cubemap)


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


這一個變量的命名和原始的註釋非常迷惑,按主流的說法,它該叫Normalized Cubemap,其作用是把當前IBL的間接漫反射亮度縮放到和從Lightmap或SH(ILC)中接收到的間接GI亮度一致。indirect_irradiance來源是物體當前像素的光照圖的亮度(靜態物體)或ILC的亮度(動態物體)。


算法簡化成公式如下:


normalized_scale = indirect_irradiance / ibl_average_brightness


一般的最終specular_IBL計算公式(UE4不完全一樣,見步驟3說明):


specular_IBL = sampled_cube * normalized_scale


其中ibl_average_brightness來源是當前經過卷積後的cubemap所計算出來的平均亮度,即roughness為1時所採的這張1*1的最小mipmap的亮度。


UE4中計算該IBL亮度時不是使用普通的亮度計算公式,而是使rgb的貢獻平均化。


ibl_average_brightness = dot(color.rgb ,float3(0.333,0.333,0.333)


UE4的亮度計算之所以使用均值,是因為如果使用標準的亮度計算公式,無法處理一些特殊的IBL邊界情形:當IBL中只存在藍色時,亮度會非常小,而當IBL中只存在綠色時,亮度又會非常大。但這麼做卻也並不是最優的選擇,我們將在第四部分進行說明。


3. 插值得到最終的縮放係數


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


注意到步驟1中結論:Roughness值越大,MixingAlpha越大,當Roughness大於等於結束Roughness時,縮放係數完全等於步驟2中計算出來的normalized_scale;當roughness小於等於開始Roughnes

時,縮放係數等於1; 縮放係數其它情形下處於[1,normalized_scale]之間。


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


由於在大部分情形下,normalized_scale小於1,所以也就可以看到一個現象:越光滑的物體,反射球對它的影響就越強,越粗糙的物體,反射球對它的亮度影響就越弱。



至於場景美術所提第二個問題——“金屬物體上的陰影總是比非金屬上的陰影來得更黑”,經查是TA做了一個很不PBR的材質規範:非金屬材質沒有AO通道,AO圖直接乘到了BaseColor上;金屬材質留有材質AO通道輸入了AO圖,BaseColor上不帶光照和遮擋信息。由於材質AO會同時作用於BaseColor和間接光照的亮度(indirect_irradiance *= AO),所以金屬物體的IBL縮放值會更小,從而更黑。


UE4移動端IBL的可用優化


1. 壓縮格式 :UE4移動端的Skylight cubemap是用的float原生的圖,既然定位等同於Reflection Capture,可以考慮使用RGBM的方式同樣的壓一壓?Reflection Capture的RGBM在Cook時也不接受ASTC/ETC/PVR方式的壓縮,要不要統一壓成硬件支持的格式?(UE4.26 Preview中,移動端已實現Reflection Capture的Cubemap壓縮,壓縮格式為ETC2)


2. Cubemap轉2D紋理

:到ES3.1都不支持CubemapArray,故IBL的變化會導致動態Instance失效,是否可以一下把 Cubemap轉為經緯圖或橢球紋理,從而使用TextureArray+Custom PrimitiveData進行動態Instance合批的最大化,從而有效的降低Drawcall ?轉為2D紋理有2個小風險,經緯圖的紋理浪費比cubemap大概有30%左右,有同事在小米6/小米9實測,相對於Cubemap,經緯圖紋理採樣的CacheMiss也更高。


3.算法優化/效果優化:UE4的IBL Normalized算法使用的是標量的ibl_average_brightness和標量的indirect_irradiance來計算,這種方式計算所帶來的問題:


在未開啟Lightmap方向性的情況下,lightmap的亮度只會計算上半圖的亮度,而ILC中的亮度可能來自於任意方向,這可能會給使用ILC和Lightmap的物體帶來不同的IBL亮度。


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


由於ibl_average_brightness和indirect_irradiance兩部分的亮度計算公式不一致,normalized的效果可能會因間接GI的顏色而使IBL反射出來的亮度不一致。


由於ibl_average_brightness來源於cubemap全球面的總和,而indirect_irradiance只可能來源於當前像素法向上半球的輸入,故其亮度normalized的結果並不會使兩者的亮度相等,而可能會導致IBL未能表現出應有的亮度,同時也會降低IBL的方向性。


MobileComputeMixingWeight的MixingWeight計算代碼實現上未防止越界~~


COD黑色行動和戰神4中的IBL Normalize使用的是3階SH


技術乾貨丨用UE4研發手遊時,如何渲染與優化環境反射?


分享到:


相關文章: