Unity線性空間下移動設備上烘焙變暗問題處理

原文鏈接:Unity線性空間下移動設備上烘焙變暗問題處理 - Blog

這是侑虎科技第263篇原創文章,感謝作者賈偉昊供稿,歡迎轉發分享,未經作者授權請勿轉載。當然,如果您有任何獨到的見解或者發現也歡迎聯繫我們,一起探討。(QQ群:465082844)n作者簡書:jianshu.com/u/5ccd10a13

前言

其實針對這個問題已經寫了一篇很簡單的填坑筆記了,但在整理本文的過程中,又想明白了一些之前沒有想明白的技術問題,算是對線性空間下烘焙場景變暗的原因有了一個真正的理解,這也是整理和寫博客的收益之一。

一、 問題確認

最近美術同學反饋在移動設備上場景感覺比平常開發的PC上要暗很多。其實之前觀察到過這個現象,當時場景的烘焙效果還沒完全確定,以為是不同的烘焙版本,或者設備屏幕亮度不同、色差等問題,一直沒有怎麼關注。

知乎有句名言——「先問是不是,再問為什麼」,那麼解決問題之前第一步要做的事情就是確認問題。

其實方法很簡單,就是排除前面提到的不同烘焙版本、設備色差等問題,使用同一烘焙版本的場景,在移動設備上進行截圖操作,然後傳輸到手機上在同一塊屏幕下進行對比,發現場景亮度差別的確很大。n

手機設備上的截圖效果

PC設備上的場景截圖效果

二、 原因排查

確認問題的確存在,而且很嚴重之後,我們先做一系列的排查實驗來檢查問題可能出現的原因。

1)首先在同一屏幕下對截圖進行對比,觀察是否是全屏變暗,發現使用實時光照的角色沒有這個問題,因為對比的關係反而會覺得角色更亮(請忽略上面截圖中的角色灰色問題,那個是因為在我的MX4 Pro上材質兼容性存在問題導致的),排除可能是某些錯誤的後處理導致全屏變暗的可能,基本確認是場景Lightmap相關的問題。

2)檢查Unity Editor中切換到安卓平台,直接運行沒有問題,但是如果載入AssetBundle包進行運行,就出現了和設備上一樣的問題。推測可能和AssetBundle的打包相關,但是無法確認更多細節。

3)製作一個簡單的Demo,使用非AssetBundle的方式進行打包,放置到手機上進行截圖對比,發現也存在變暗的問題,排除單純的AssetBundle打包導致的問題;

4)在Demo中,將我們自己的材質替換為Standard材質,進行打包對比測試,依然有變暗的問題,排除我們項目自身材質Bug。

5)因為我們在移動設備上使用了線性空間,因此懷疑可能是線性空間導致的,切換顏色空間進行對比實驗,發現伽馬空間下烘焙效果也有色差,但是沒有線性空間明顯。

經過這一系列的實驗對照之後,我有點懷疑這是一個Unity的「特性(bug)」了。。。順便說句題外話,我們使用的是比較新的Unity 5.5.4f版本,之所以從之前較為穩定的5.3.6版本升級上來,很重要的原因之一是我們想要在移動設備上使用線性空間。

三、 資料檢索

問題的矛頭指向了烘焙,又是Standard材質也會存在的問題,那麼猜測網路上應該有不少問題反饋和相關資料,於是照常進行一波相關資料的檢索和收集。

3.1 Unity Issue Tracker

n首先懷疑是否是Unity的官方「特性(Bug)」,搜索了一下issuetracker,果然找到一個似乎相關的Bug彙報BAKED LIGHTMAP IS DARKER ON ANDROID PLATFORM COMPARED TO PC PLATFORM,說是Fixed in Unity 5.5.5,沒提到線性空間的設置,我們7月份要測試也等不到5.5.5版本了,所以暫時先記錄下,繼續。

3.2 UWA問答

nUWA的問答模塊有一個帖子問過相關問題:Lightmap在PC上顯示正常,但是轉到Android平台上存在色差,顏色普遍偏暗。這裡引用一下回答的內容:一般來講,有兩種情況可能會導致色偏和亮度差異。一般來講,有兩種情況可能會導致色偏和亮度差異。

1)Unity烘焙的Lightmap是32bit的HDR圖,而移動設備通常不支持HDR圖(32bit per channel),會按照LDR圖(8bit per channel)的形式進行處理,因此會出現色偏問題。因此我們建議:

  • 在移動平台下使用Mobile/Diffuse材質,可載入Standard Assets(Mobile) package獲得。如果要獲得更合適的效果,需要自行修改Lightmap的DecodeLightmap函數,該函數可在UnityEditorDataCGIncludesUnityCG.cginc文件中找到。需要說明的是,這種方法也不能達到與PC端完全一致的效果。
  • 如果需要PC和移動平台的顯示效果一致,可以用圖像編輯軟體修改Lightmap為LDR格式,例如PNG(8bit per channel)。
  • 為了避免類似問題,請不要使用過於強烈的Light進行烘焙,因為Light的強度(Intensity)越高,色偏問題會越嚴重。若有陰影丟失時,可以嘗試檢查一下模型的Lightmapindex、Lightmapscaleoffset、UV2等影響Lightmap採樣的一些參數。

2)另一種可能是存在過曝現象,可以嘗試將playersettings -> use direct3d 11關閉,看問題是否解決。

這個答案給出了差異產生的原因,即Lightmap貼圖是使用exr格式存儲的,然後在移動設備上如果按照LDR進行處理產生偏差是很正常的,給出的建議我們看了下,沒有對應的問題。自行修改UnityCG.cginc的事情前段時間幫美術干過,但是沒有很好的方法可以讓團隊內部所有成員機器上的效果保證一致,所以一開始不是非常想採用,而且如何修改效果才能正確這個答案給得並不非常明確。

3.3 博客

n接著,找到一篇《解決Lightmap在PC上與ios和Android上表現不同的問題》,使用LogLuv這種編碼格式對exr文件進行重新的編碼。文章中有基本的原理分析,有解決方案,還有源碼,看上去很靠譜。做了一個demo嘗試走了一遍流程,是可以解決變暗的問題,但是和PC上比,還會變得更亮……依然有偏差,不知道是否是線性空間的問題,而且更加重要的是過程太多繁瑣,維護成本很高,所有的材質都需要對應修改,不理想。況且,轉換之後的貼圖要使用RGBA8的非壓縮格式,一張1024的貼圖需要佔用4-5M左右的包體和內存空間,代價太大,不在走投無路的情況下實在不能忍。

想起不久前看過一篇知乎上的帖子:談光照圖烘焙技巧,裡面跨平台的部分有提到相關的問題:

推薦把沒有烘焙Lightmap時候的顏色大體定在0.5-0.7左右的亮度,這樣烘焙進退都可以,而且這樣Lightmap的係數不會特別誇張的大,這樣在pc平台和android基本就能保持一致。

檢查了一下,做demo用的貼圖亮度0.63,光照亮度調整為1,烘焙之前的顏色大約也符合0.5-0.7左右的亮度,不知道是否因為線性空間的問題,反正依然沒有解決。但這篇文章直接提到了UnityCG.cginc中的源碼,即lightmap的顏色解析過程,去看了一下。

// Decodes HDR texturesn// handles dLDR, RGBM formatsn// Called by DecodeLightmap when UNITY_NO_RGBM is not defined.ninline half3 DecodeLightmapRGBM (half4 data, half4 decodeInstructions)n{n // If Linear mode is not supported we can skip exponent partn #if defined(UNITY_COLORSPACE_GAMMA)n # if defined(UNITY_FORCE_LINEAR_READ_FOR_RGBM)n return (decodeInstructions.x * data.a) * sqrt(data.rgb);n # elsen return (decodeInstructions.x * data.a) * data.rgb;n # endifn #elsen return (decodeInstructions.x * pow(data.a, decodeInstructions.y)) * data.rgb;n #endifn}nn// Decodes doubleLDR encoded lightmaps.ninline half3 DecodeLightmapDoubleLDR( fixed4 color )n{n return 2.0 * color.rgb;n}nninline half3 DecodeLightmap( fixed4 color, half4 decodeInstructions)n{n#if defined(UNITY_NO_RGBM)n return DecodeLightmapDoubleLDR( color );n#elsen return DecodeLightmapRGBM( color, decodeInstructions );n#endifn}nnhalf4 unity_Lightmap_HDR;nninline half3 DecodeLightmap( fixed4 color )n{n return DecodeLightmap( color, unity_Lightmap_HDR );n}n

這段代碼還比較容易理解,在不同的宏控制下,使用不同的LightmapDecode方案,主要包括DecodeLightmapRGBM和DecodeLightmapDoubleLDR兩種。

四、HDR和LDR

這裡擴展說明一下HDR和LDR圖像的區別,注意,這裡說到的HDR不是指High Dynamic Range Rendering,而是僅僅指高動態範圍的圖像格式,當然在HDR Rendering中肯定要用到高動態範圍的圖像格式,因為和本文關係不大就不深入討論了。

前面提到的一篇博客里也說到了,在Unity里,Lightmap是以exr的格式來存儲的,即openEXR格式,這是一種開放標準的高動態範圍圖像格式,在計算機圖形學中被廣泛應用。

在LDR的圖像格式中,比如BMP、PNG、TGA等,一個像素的顏色值可能由RGBA四個通道組成,而每一個通道可以表達的範圍是0-255。也就是說8位就可以表示一個像素的一個通道值,RGBA四個通道在不壓縮的情況下只需要32位即可。而openEXR格式支持16位浮點數、32位浮點數和32位整數的像素顏色值。Unity中的Lightmap採用這種HDR格式的圖像,可以表達的範圍當然遠比LDR圖像的範圍要大得多。

在Unity中,exr格式的貼圖在導入的時候會被轉變為RGBM的格式,因為通常大家Lightmap的導入選項中的TextureImporterSettings.rgbm都是使用默認的「Auto」,即當原始數據為HDR格式的時候使用RGBM格式的編碼進行導入。RGBM把[0, 8]範圍的值壓縮成[0, 1]範圍,並且把一個係數存儲在Alpha通道中,最終的顏色值為RGBA 8。

五、最終選擇的解決方案

在經過這些探究、對比和糾結之後,我決定還是採用UWA的建議,自己修改UnityCG.cginc中的源碼。原因是這裡是產生不同的根本所在,在不需要對美術製作流程產生任何影響的前提下,也許可以從根本上解決問題。

烘焙場景變暗的原因前文的代碼和搜集的博客中已經給出了——在PC平台上,因為是支持RGBM格式的,而且我們開啟了線性空間,因此DecodeLightmap最終走了下面代碼的邏輯:

return (decodeInstructions.x * pow(data.a, decodeInstructions.y)) * data.rgb;n

這裡decodeInstructions的值是在外部定義的unity_Lightmap_HDR。關於這個變數我沒有從Unity的文檔中搜索到相關的設置,只在UnityCG.cginc中看到一個似乎相關定義:

// These constants must be kept in sync with RGBMRanges.hn#define LIGHTMAP_RGBM_SCALE 5.0n

在移動設備上,最終會使用這樣的邏輯:

// Decodes doubleLDR encoded lightmaps.ninline half3 DecodeLightmapDoubleLDR( fixed4 color )n{n return 2.0 * color.rgb;n}n

也就是值直接乘以了2.0作為Lightmap的顏色值,不知道這個2.0是從科學的計算方法得出的,還是僅僅是一個接近的經驗值。而在線性空間下,按照RGBM的計算方法,RGB A 8材質最終的值。由於線性空間的亮度是線性增長的,因此2倍和8倍其實有非常大的差距,明暗差別很大也就可以理解了。當然這裡我進行測試,decodeInstructions.x的值並不是8,而似乎是4或者5這樣的值。我用Photoshop打開Unity的Lightmap文件,發現其中的alpha通道幾乎全部接近純白色或者純黑色,只有部分是灰色的,像下圖中用於測試的Lightmap圖。n

用於測試的Lightmap原圖

對應的Alpha通道顏色

檢查了下我們自己用的場景中的Lightmap貼圖,無論是室外場景還是室內場景,光照貼圖的Alpha通道都是接近純白色或者純黑色,部分地方是灰色,那麼如果我仿照對於RGBM格式貼圖的處理,捨棄掉對於alpha的乘法,結果應該只會丟失掉一些局部細節而已,不會對整體的明暗效果產生影響。於是,我選擇了最終的解決方案——將DecodeLightmapDoubleLDR函數中的2.0修改為unity_Lightmap_HDR.x的值。n總結一下最終的解決方案:

1)修改BuildinShader中的UnityCG.cginc中的DecodeLightmapDoubleLDR函數為:

// Decodes doubleLDR encoded lightmaps.ninline half3 DecodeLightmapDoubleLDR( fixed4 color )n{n#if defined(UNITY_COLORSPACE_GAMMA)n // Gamma空間下依然使用之前的計算方法n return 2.0 * color.rgb;n#elsen // 線性空間下使用和RGBM近似的方式n return unity_Lightmap_HDR.x * color.rgb;n#endifn}n

2)重新替換Unity Editor的UnityCG.cginc文件,刪除工程中Libery下緩存的編譯好的Shader,即整個ShaderCache目錄,重啟Unity讓它重新編譯所有Shader。

3)編寫資源導入的後處理腳本,設置Android和iOS設備下的Lightmap的導入格式為ETC2_RGB4和PVRTC_RGB,重新導入所有光照貼圖。這裡捨棄掉alpha通道,在安卓設備上,讓之前一張1024*1024大小的貼圖從1.3M變為0.7M,意外的收穫,開心~(另外我在安卓設備上嘗試了ETC2_RGBA8的格式,結果反而全黑掉了,因為是在中間嘗試的步驟,因此沒有深究。這裡提醒一下,如果使用同樣方法修改shader的同學可能需要注意下光照貼圖壓縮格式的問題。)

4)重新生成所有相關的AssetBundle文件,包括Shader、光照貼圖,打包到安卓設備上,然後截圖傳到PC同屏對比,美術看完之後表示——「很完美,看上去和PC上的完全一樣!」。

放一張修改之後在PC上使用安卓平台運行AssetBundle版本的遊戲截圖。n

最終修復後的效果圖

六、總結

這個問題的根本原因在整理這篇文章的時候我也進行了一些思考,應該是2.0這個經驗值是針對伽馬空間下的光照貼圖亮度調整的經驗值,伽馬空間的亮度疊加是非線性的,2.0隻能是一個接近,因此無法做到各個平台下的效果一致。在移動平台支持線性空間之後,UnityCG.cginc並沒有更新這個值在線性空間下的表現問題,導致烘焙的結果在設備上會變暗。

這個問題的解決花費了我大約兩天的時間,中間進行各種資料的搜索、實驗、方案對比,以及和美術進行問題的討論和最終解決方案的效果確認。最終的解決方案雖然只修改了幾行代碼,但是尋找解決方案的過程卻涉及到了各種知識點。工程中,解決問題的過程總是伴隨著各種猜測和不解,在最後問題解決了之後,有些疑問解開了,有些疑問可能仍然沒解開,比如我依然不知道Unity在哪裡可以設置unity_Lightmap_HDR的值,或者說這個變數的值是由什麼來決定的。最終方案的選擇是基於時間成本、維護成本、最終效果一致等各個方面的考慮,我也很明確地知道這個解決方案並不一定適合所有的項目,抑或這個解決方案的背後是否還隱藏著別的坑。但是如美術看到結果時所說——「很完美,看上去和PC上的完全一樣!」正應了計算機圖學上的那句名言:如果看上去是對的,那它就是對的。

七、問題解決後的安利:線性空間的工作流

本文到最後,我突然理解了為什麼網上那麼多解決方案在建議規定貼圖亮度範圍,又或者如UWA的問答中所說的「需要自行修改Lightmap的DecodeLightmap函數,該函數可在UnityEditorDataCGIncludesUnityCG.cginc文件中找到。需要說明的是,這種方法也不能達到與PC端完全一致的效果。 」

而我們在解決這個問題之後,做到了動態光影場景效果接近最終烘焙效果,PC預覽烘焙效果幾乎等於移動設備上忽略色差和亮度差別之後的效果,因為我們使用的是線性空間的工作流啊~~

之前在網易做端游項目《無盡戰區》的時候,就經歷過一次從伽馬空間到線性空間製作方案的改變,當時已經了解到線性空間對於美術製作和最終效果的影響力。因此在了解到Unity 5.5開始在移動設備上原生支持線性空間的時候,我們就在5.5.1f版本的時候在工作室內推行了線性空間的工作流方法。由於之前角色製作已經是在用Substance和線性空間來做了(角色材質中模擬的線性方案),因此只在場景製作這邊遇到了一些阻力,但當時為了解決烘焙效果幾乎無法預覽,全靠猜、重試和經驗的問題,頂住壓力和主美一起推行了完整的線性空間工作流。除了效果之外,這次問題的解決也讓我再次體會到了線性空間的優勢——雖然問題很可能是由於Unity官方在材質中對於設備上的結果計算沒有考慮線性空間導致的,但是我們自己修復之後,可以做到設備上的效果與PC預覽的效果基本一致,這對於美術的效果調整有很大的信心提升。雖然安卓設備上屏幕參數各種不同,但是我們可以保證在色准較好的設備上效果是穩定的。

關於線性空間的原理和優劣晚上有大把TA、或者程序的文章在講,包括Unity官方文檔也有詳細的說明,這裡只放一張官方的對比圖,有興趣的朋友可以自己搜索。n

線性空間和伽馬空間對比

當然,線性空間也有代價,就是一點額外的性能消耗。Unity 5.5版本開始支持移動設備上線性空間,需要項目組付出的代價是只支持OpenGLES 3.0以上的設備,對應最低的安卓系統版本是4.3。

一方面,支持OpenGLES 3.0的設備佔比越來越高,另外一方面,安卓模擬器貌似還都大都是只支持2.0的版本。這也是我們項目後面可能要面臨的問題,但是有舍才有得,從項目整體的收益來看,目前使用線性空間還是利大於弊。

最後,安利最近立項的Unity手游項目組,可以考慮使用線性空間工作流,你們的美術會愛你的。

感謝賈偉昊的分享,如果您有任何獨到的見解或者發現也歡迎聯繫我們,一起探討。(QQ群:465082844)。n也歡迎大家來積极參与U Sparkle開發者計劃,簡稱"US",代表你和我,代表UWA和開發者在一起!

推薦閱讀:

UWA GOT v1.1 | 支持本地管理深度測評、全新的UWA API、兼容Unity 2017.3
Unity載入模塊深度解析(網格篇)
ET框架:windows/Linux 雲伺服器部署
棋盤格與幻影坦克

TAG:Unity游戏引擎 | 线性空间 | 游戏 |