分享

Unity5中lightmap的坑

 勤奋不止 2016-07-29

 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

[csharp] view plaincopy
  1. using UnityEngine;  
  2. using UnityEditor;  
  3. using System.Collections;  
  4. using System.Collections.Generic;  
  5.   
  6. public class PrefabLightmapDataEditor : Editor  
  7. {  
  8.     // 把renderer上面的lightmap信息保存起来,以便存储到prefab上面  
  9.     [MenuItem("GameObject/Lightmap/Save"false, 0)]  
  10.     static void SaveLightmapInfo ()  
  11.     {  
  12.         GameObject go = Selection.activeGameObject;  
  13.         if (go == nullreturn;  
  14.         PrefabLightmapData data = go.GetComponent<PrefabLightmapData>();  
  15.         if (data == null) {  
  16.             data = go.AddComponent<PrefabLightmapData>();  
  17.         }  
  18.   
  19.         data.SaveLightmap();  
  20.         EditorUtility.SetDirty(go);  
  21.     }  
  22.   
  23.     // 把保存的lightmap信息恢复到renderer上面  
  24.     [MenuItem("GameObject/Lightmap/Load"false, 0)]  
  25.     static void LoadLightmapInfo()  
  26.     {  
  27.         GameObject go = Selection.activeGameObject;  
  28.         if (go == nullreturn;  
  29.   
  30.         PrefabLightmapData data = go.GetComponent<PrefabLightmapData>();  
  31.         if (data == nullreturn;  
  32.   
  33.         data.LoadLightmap();  
  34.         EditorUtility.SetDirty(go);  
  35.   
  36.         new GameObject();  
  37.     }  
  38.   
  39.     [MenuItem("GameObject/Lightmap/Clear"false, 0)]  
  40.     static void ClearLightmapInfo()  
  41.     {  
  42.         GameObject go = Selection.activeGameObject;  
  43.         if (go == nullreturn;  
  44.   
  45.         PrefabLightmapData data = go.GetComponent<PrefabLightmapData>();  
  46.         if (data == nullreturn;  
  47.   
  48.         data.m_RendererInfo.Clear();  
  49.         EditorUtility.SetDirty(go);  
  50.     }  
  51. }  


PrefabLightmapData.cs

[csharp] view plaincopy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System.Collections.Generic;  
  4. using UnityEngine.Rendering;  
  5.   
  6. public class PrefabLightmapData : MonoBehaviour  
  7. {  
  8.     [System.Serializable]  
  9.     public struct RendererInfo  
  10.     {  
  11.         public Renderer     renderer;  
  12.         public int          lightmapIndex;  
  13.         public Vector4      lightmapOffsetScale;  
  14.     }  
  15.       
  16.     public List<RendererInfo> m_RendererInfo;  
  17.   
  18.     void Awake ()  
  19.     {  
  20.         LoadLightmap();  
  21.         Debug.Log("--------------------------------------------");  
  22.         var renderers = GetComponentsInChildren<MeshRenderer>();  
  23.         foreach (var item in renderers) {  
  24.             Debug.Log(item.lightmapIndex);  
  25.         }  
  26.   
  27.         //      if (m_RendererInfo == null || m_RendererInfo.Count == 0)  
  28.         //          return;  
  29.         //        
  30.         //      var lightmaps = LightmapSettings.lightmaps;  
  31.         //      var combinedLightmaps = new LightmapData[lightmaps.Length + m_Lightmaps.Count];  
  32.         //        
  33.         //      lightmaps.CopyTo(combinedLightmaps, 0);  
  34.         //      for (int i = 0; i < m_Lightmaps.Count; i++)  
  35.         //      {  
  36.         //          combinedLightmaps[i+lightmaps.Length] = new LightmapData();  
  37.         //          combinedLightmaps[i+lightmaps.Length].lightmapFar = m_Lightmaps[i];  
  38.         //      }  
  39.         //        
  40.         //      ApplyRendererInfo(m_RendererInfo, lightmaps.Length);  
  41.         //      LightmapSettings.lightmaps = combinedLightmaps;  
  42.     }  
  43.   
  44.     public void SaveLightmap()  
  45.     {  
  46.         m_RendererInfo.Clear();  
  47.   
  48.         var renderers = GetComponentsInChildren<MeshRenderer>();  
  49.         foreach (MeshRenderer r in renderers) {  
  50.             if (r.lightmapIndex != -1) {  
  51.                 RendererInfo info = new RendererInfo();  
  52.                 info.renderer = r;  
  53.                 info.lightmapOffsetScale = r.lightmapScaleOffset;  
  54.                 info.lightmapIndex = r.lightmapIndex;  
  55.   
  56. //                Texture2D lightmap = LightmapSettings.lightmaps[r.lightmapIndex].lightmapFar;  
  57. //  
  58. //                info.lightmapIndex = m_Lightmaps.IndexOf(lightmap);  
  59. //                if (info.lightmapIndex == -1) {  
  60. //                    info.lightmapIndex = m_Lightmaps.Count;  
  61. //                    m_Lightmaps.Add(lightmap);  
  62. //                }  
  63.   
  64.                 m_RendererInfo.Add(info);  
  65.             }  
  66.         }  
  67.     }  
  68.   
  69.     public void LoadLightmap()  
  70.     {  
  71.         if (m_RendererInfo.Count <= 0) return;  
  72.   
  73.         foreach (var item in m_RendererInfo) {  
  74.             item.renderer.lightmapIndex = item.lightmapIndex;  
  75.             item.renderer.lightmapScaleOffset = item.lightmapOffsetScale;  
  76.         }  
  77.     }  
  78. }  

            场景中的光照贴图的设置跟之前版本一样,直接设置LightmapSetting中的lightmap数组就可以了,光照贴图同样可以打包成assetbundle,只要能够正确的加载并设置就可以。

            如果我们有做地图动态生成、动态加载等需求,那么就必须要自己处理lightmap的加载。大体思路是将场景中物体的光照信息(lightmapindex等)和当前场景的光照贴图(lightmapsetting中获取)保存成一个配置,然后运行时自己加载这些信息。不过这样做有一个前提是场景中的物体一定不能做成prefab,原因如前文所述。

            如果不考虑场景的动态更新,那么就简化很多。一个场景一个Scene,每个场景自己烘焙,然后使用LoadLevelAdditive加载场景就可以了。Unity可以正确处理好光照贴图的合并和索引的更新。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多