Unity5中lightmap的坑
Unity5中光照系統替換為Enlighten是非常大的革新。但是對手游來說,好處還未享受到,坑先踩上了。並且是我研究了兩天都沒有很好的解決辦法的深坑。
我並沒有系統的學過圖形學,所以以下所說的內容都是自己的理解,可能存在錯誤的地方,敬請見諒。
所謂lightmap,就是用一組預先烘焙好的貼圖來替代執行時光影計算。在Unity5之前使用的是beast系統,Unity5使用的是enlighten系統,新系統的好處是支援執行時光照計算,支援全域性光。
作為使用者來說就是兩個部分,一個是烘焙的部分,一個是載入的部分。
Unity5的lighting分頁中有兩個勾選,一個是precompute GI,一個是baked GI。一般來說手遊還是無法承載precompute的計算量,從效率的角度考慮這兩個GI系統在一個場景中只開啟一個。我們使用baked GI,其實也就跟之前的光照系統差不多了。另外烘焙的過程真心慢(萬一同時勾選了兩個GI,那麼就是慢上加慢),GI Cache資料夾如果不限制大小的話,動輒幾十G,可見其計算量。
烘焙如果勾上auto則所有的烘焙結果存在GI Cache資料夾下,一般這種模式沒有什麼價值。點選Bake按鈕進行烘焙,烘焙完成後會生成跟場景同名的資料夾,裡面有光照貼圖和一個lightmapsnapshot.asset檔案。這個檔案就是第一個坑。
光照貼圖的載入原理其實很簡單,每個renderer中記錄了一個lightmapindex和lightmapscaleoffset,LightmapSetting中有一個全域性的光照貼圖的陣列,包含當前場景中所有光照貼圖的索引。每個renderer根據index和offset確定自己應該使用光照貼圖的哪個部分,最終渲染出實際帶光影的效果。
原本這些資料是存在每個renderer裡面的,也即存在scene或者是prefab中。但是Unity5為了多場景合作編輯,把這些資料移到snapshot檔案中了。場景中不再儲存這些資訊,snapshot中儲存了場景中的renderer對應的光照資料是什麼。但是Unity並沒有提供訪問snapshot的方法。所以原本很簡單的問題在這裡變得非常噁心。
由於snapshot中儲存的是當前場景中的renderer的資訊,所以拷貝新的renderer或者在程式碼中例項化一個物體都是沒有光照資訊的。如果場景中的物體儲存為prefab,則光照資訊也會丟失,因為此時場景中關聯的是一個prefab檔案,而場景物件是儲存在prefab中的。
補充說明,snapshot中根據GameObject的udid來儲存對應的光照資訊。所以只要udid不變,則光照資訊正常,只要改變,則光照資訊丟失。所以新例項化的物體是沒有光照資訊的,這個要自己手動設定(其實也很簡單,把原物體所有Renderer中的光照資訊賦值給新物體中對應的Renderer就好)。 如果烘焙的時候物體不是Prefab,後來儲存為Prefab,或者原來烘焙的時候是Prefab,後來取消Prefab的關聯,這兩個操作都會使光照資訊丟失。
一個簡單的解決辦法是將光照資訊儲存在一個元件上面,然後載入場景或者物體的時候再恢復。程式碼如下:
PrefabLightmapDataEditor.cs
- using UnityEngine;
- using UnityEditor;
- using System.Collections;
- using System.Collections.Generic;
- public class PrefabLightmapDataEditor : Editor
- {
- // 把renderer上面的lightmap資訊儲存起來,以便儲存到prefab上面
- [MenuItem("GameObject/Lightmap/Save", false, 0)]
- static void SaveLightmapInfo ()
- {
- GameObject go = Selection.activeGameObject;
- if (go == null) return;
- PrefabLightmapData data = go.GetComponent<PrefabLightmapData>();
- if (data == null) {
- data = go.AddComponent<PrefabLightmapData>();
- }
- data.SaveLightmap();
- EditorUtility.SetDirty(go);
- }
- // 把儲存的lightmap資訊恢復到renderer上面
- [MenuItem("GameObject/Lightmap/Load", false, 0)]
- static void LoadLightmapInfo()
- {
- GameObject go = Selection.activeGameObject;
- if (go == null) return;
- PrefabLightmapData data = go.GetComponent<PrefabLightmapData>();
- if (data == null) return;
- data.LoadLightmap();
- EditorUtility.SetDirty(go);
- new GameObject();
- }
- [MenuItem("GameObject/Lightmap/Clear", false, 0)]
- static void ClearLightmapInfo()
- {
- GameObject go = Selection.activeGameObject;
- if (go == null) return;
- PrefabLightmapData data = go.GetComponent<PrefabLightmapData>();
- if (data == null) return;
- data.m_RendererInfo.Clear();
- EditorUtility.SetDirty(go);
- }
- }
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine.Rendering;
- public class PrefabLightmapData : MonoBehaviour
- {
- [System.Serializable]
- public struct RendererInfo
- {
- public Renderer renderer;
- public int lightmapIndex;
- public Vector4 lightmapOffsetScale;
- }
- public List<RendererInfo> m_RendererInfo;
- void Awake ()
- {
- LoadLightmap();
- Debug.Log("--------------------------------------------");
- var renderers = GetComponentsInChildren<MeshRenderer>();
- foreach (var item in renderers) {
- Debug.Log(item.lightmapIndex);
- }
- // if (m_RendererInfo == null || m_RendererInfo.Count == 0)
- // return;
- //
- // var lightmaps = LightmapSettings.lightmaps;
- // var combinedLightmaps = new LightmapData[lightmaps.Length + m_Lightmaps.Count];
- //
- // lightmaps.CopyTo(combinedLightmaps, 0);
- // for (int i = 0; i < m_Lightmaps.Count; i++)
- // {
- // combinedLightmaps[i+lightmaps.Length] = new LightmapData();
- // combinedLightmaps[i+lightmaps.Length].lightmapFar = m_Lightmaps[i];
- // }
- //
- // ApplyRendererInfo(m_RendererInfo, lightmaps.Length);
- // LightmapSettings.lightmaps = combinedLightmaps;
- }
- public void SaveLightmap()
- {
- m_RendererInfo.Clear();
- var renderers = GetComponentsInChildren<MeshRenderer>();
- foreach (MeshRenderer r in renderers) {
- if (r.lightmapIndex != -1) {
- RendererInfo info = new RendererInfo();
- info.renderer = r;
- info.lightmapOffsetScale = r.lightmapScaleOffset;
- info.lightmapIndex = r.lightmapIndex;
- // Texture2D lightmap = LightmapSettings.lightmaps[r.lightmapIndex].lightmapFar;
- //
- // info.lightmapIndex = m_Lightmaps.IndexOf(lightmap);
- // if (info.lightmapIndex == -1) {
- // info.lightmapIndex = m_Lightmaps.Count;
- // m_Lightmaps.Add(lightmap);
- // }
- m_RendererInfo.Add(info);
- }
- }
- }
- public void LoadLightmap()
- {
- if (m_RendererInfo.Count <= 0) return;
- foreach (var item in m_RendererInfo) {
- item.renderer.lightmapIndex = item.lightmapIndex;
- item.renderer.lightmapScaleOffset = item.lightmapOffsetScale;
- }
- }
- }
場景中的光照貼圖的設定跟之前版本一樣,直接設定LightmapSetting中的lightmap陣列就可以了,光照貼圖同樣可以打包成assetbundle,只要能夠正確的載入並設定就可以。
如果我們有做地圖動態生成、動態載入等需求,那麼就必須要自己處理lightmap的載入。大體思路是將場景中物體的光照資訊(lightmapindex等)和當前場景的光照貼圖(lightmapsetting中獲取)儲存成一個配置,然後執行時自己載入這些資訊。不過這樣做有一個前提是場景中的物體一定不能做成prefab,原因如前文所述。
如果不考慮場景的動態更新,那麼就簡化很多。一個場景一個Scene,每個場景自己烘焙,然後使用LoadLevelAdditive載入場景就可以了。Unity可以正確處理好光照貼圖的合併和索引的更新。
相關文章
- 在unity5中減少Draw Calls(SetPass Calls)[轉]Unity
- Unity5 AssetBundle的一些整理(一)Unity
- 使用 GPU 進行 Lightmap 烘焙 - 簡單 demoGPU
- CEF 中的那些坑
- 盜墓中的坑
- Unity5 PBR如何實現天氣系統的雪景效果Unity
- Unity乾貨知識:加速Lightmap烘焙速度的一些小技巧Unity
- 烘焙法線中的坑
- 列表與字典中的坑
- node編碼中的坑
- 小心 Enum Parse 中的坑
- nginx配置gzip中的坑Nginx
- Storyboard佈局中的坑
- Maven配置中遇到的坑Maven
- Java中浮點數的坑Java
- JasperReport 中踩過的坑
- Hyperledger Fabric部署的坑(更新中)
- Fragment中呼叫startActivityForResult的那些坑Fragment
- Vue 3.0 使用 Vuetify中的坑Vue
- VUE 使用中踩過的坑Vue
- MongoDB中的一些坑MongoDB
- Redis中的Scan命令踩坑記Redis
- HTTP 規範中的那些暗坑HTTP
- Java 中,Arrays 轉 List 的那些坑Java
- Zuul中聚合Swagger的坑ZuulSwagger
- ajax中回撥的幾個坑
- mysql中null與“空值”的坑MySqlNull
- Oracle資料庫中遇到的坑Oracle資料庫
- 服務端介面中的那些坑服務端
- Java中ArrayList remove會遇到的坑JavaREM
- lambda匿名函式使用中的坑函式
- AngularJS 中的一些坑AngularJS
- thrift中的超時(timeout)坑
- vue專案中踩過的element的坑Vue
- MySQL中wait_timeout的坑MySqlAI
- Fragment中的那些坑——Android進階FragmentAndroid
- vue中style下scope的使用和坑Vue
- 那些前端工作中遇到的坑(01)前端