在很多情况下,我们为了提高渲染效率,一般都会让美术同学在制作场景时,设置场景相关节点的lightmap static属性,提前给整个场景烘培出静态的光照贴图lightmap,烘培的数据保存在场景目录下的LightmapSnapshot文件中,主要包括的数据有:
lightmaps:烘培出的光照贴图数组;
gameobject uid:被烘培的gameobject的唯一标识;
renderer的lightmapIndex:被烘培的gameobject的renderer组件所指向的光照贴图数组的索引;
renderer的lightmapScaleOffset:被烘培的gameobject的renderer组件所指向的光照贴图用于采样的区域坐标和宽高;
这个文件目前没有相关api读写,如果你想烘培完场景之后,把场景里面的gameobjet抽出来做成prefab,等切换完场景之后再用于动态加载是不可行的,因为抽出来的prefab在Instantiate之后将会是一个新的gameobject,uid自然和LightmapSnapshot文件里面记录的不一样,导致找不到对应的光照数据而造成模型没光照变暗或渲染错乱。
还有一种比较常见的需求是,在游戏运行时,通过更换光照贴图数据,营造场景在不同时间或季节的光照氛围,例如白天和黑夜等。
so,就算场景烘培完之后,我们还是要“动”它。
做法大概是,既然LightmapSnapshot文件我们不能动,那就把上面提到的光照数据保存到我们可以控制的文件里面,例如prefab。
首先,给场景根节点挂一个自定义组件,用于保存烘培出的光照贴图数组和烘培模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | using UnityEngine; using System.Collections; [ExecuteInEditMode] public class SceneLightMapSetting : MonoBehaviour { public Texture2D []lightmapFar, lightmapNear; public LightmapsMode mode; public void SaveSettings() { mode = LightmapSettings.lightmapsMode; lightmapFar = null; lightmapNear = null; if (LightmapSettings.lightmaps != null && LightmapSettings.lightmaps.Length > 0) { int l = LightmapSettings.lightmaps.Length; lightmapFar = new Texture2D[l]; lightmapNear = new Texture2D[l]; for (int i = 0; i < l; i++) { lightmapFar[i] = LightmapSettings.lightmaps[i].lightmapFar; lightmapNear[i] = LightmapSettings.lightmaps[i].lightmapNear; } } RendererLightMapSetting[] savers = Transform.FindObjectsOfType<RendererLightMapSetting>(); foreach(RendererLightMapSetting s in savers) { s.SaveSettings(); } } public void LoadSettings() { LightmapSettings.lightmapsMode = mode; int l1 = (lightmapFar == null) ? 0 : lightmapFar.Length; int l2 = (lightmapNear == null) ? 0 : lightmapNear.Length; int l = (l1 < l2) ? l2 : l1; LightmapData[] lightmaps = null; if (l > 0) { lightmaps = new LightmapData[l]; for (int i = 0; i < l; i++) { lightmaps[i] = new LightmapData(); if (i < l1) lightmaps[i].lightmapFar = lightmapFar[i]; if (i < l2) lightmaps[i].lightmapNear = lightmapNear[i]; } LightmapSettings.lightmaps = lightmaps; } } void OnEnable() { #if UNITY_EDITOR UnityEditor.Lightmapping.completed += SaveSettings; #endif } void OnDisable() { #if UNITY_EDITOR UnityEditor.Lightmapping.completed -= SaveSettings; #endif } void Awake () { if(Application.isPlaying){ LoadSettings(); } } } |
再给场景里面被烘培的gameobject挂一组件,用于保存光照贴图数组的索引和光照贴图的区域坐标和宽高数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | using UnityEngine; using System.Collections; [ExecuteInEditMode] public class RendererLightMapSetting : MonoBehaviour { public int lightmapIndex; public Vector4 lightmapScaleOffset; public void SaveSettings() { if(!IsLightMapGo(gameObject)){ return; } Renderer renderer = GetComponent<Renderer>(); lightmapIndex = renderer.lightmapIndex; lightmapScaleOffset = renderer.lightmapScaleOffset; } public void LoadSettings() { if(!IsLightMapGo(gameObject)){ return; } Renderer renderer = GetComponent<Renderer>(); renderer.lightmapIndex = lightmapIndex; renderer.lightmapScaleOffset = lightmapScaleOffset; } public static bool IsLightMapGo(GameObject go){ if(go == null){ return false; } Renderer renderer = go.GetComponent<Renderer>(); if(renderer == null){ return false; } return true; } void Awake () { if (Application.isPlaying) { LoadSettings (); } } } |
如果手动挂上面两个脚本的话,繁琐且容易出错,so,写工具接口,用工具扫场景挂脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | using UnityEngine; using UnityEditor; using System.IO; using System.Text; using System.Linq; using System.Collections; using System.Collections.Generic; using System; public class SceneTools { static string[] _sNeedAssetType = new string[1]{ ".unity", }; [MenuItem("场景相关/1. 保存场景光照贴图信息", false, 111)] public static void SaveSceneMapLightSetting() { UnityEngine.Object[] selObjs = Selection.GetFiltered (typeof(UnityEngine.Object), SelectionMode.DeepAssets); if (selObjs == null || selObjs.Length == 0) { Debug.LogError ("请到\"Scenes\" 目录下选中需要保存场景光照贴图信息的场景!"); return; } string assetPath; UnityEngine.Object assetObj = null; GameObject gameObj = null; SceneLightMapSetting slms = null; RendererLightMapSetting rlms = null; bool needSave = false; for (int i = 0; i < selObjs.Length; ++i) { needSave = false; assetPath = AssetDatabase.GetAssetPath (selObjs [i]); foreach (string extName in _sNeedAssetType) { if (assetPath.EndsWith (extName)) { needSave = true; break; } } if (!needSave) continue; assetObj = AssetDatabase.LoadAssetAtPath (assetPath, typeof(UnityEngine.Object)) as UnityEngine.Object; EditorApplication.OpenScene (assetPath); gameObj = GameObject.Find ("scene_root"); if (gameObj == null) { Debug.LogError ("不合法的场景:场景没有scene_root根节点!!"); continue; } slms = gameObj.GetComponent<SceneLightMapSetting>(); if(slms == null){ slms = gameObj.AddComponent<SceneLightMapSetting>(); } Renderer[] savers = Transform.FindObjectsOfType<Renderer>(); foreach(Renderer s in savers) { if(s.lightmapIndex != -1){ rlms = s.gameObject.GetComponent<RendererLightMapSetting>(); if(rlms == null){ rlms = s.gameObject.AddComponent<RendererLightMapSetting>(); } } } slms.SaveSettings(); EditorApplication.SaveScene(); Debug.Log(string.Format("场景{0}的光照贴图信息保存完成", assetObj.name)); } EditorApplication.SaveAssets(); } } |
没截图从觉得少点什么,来,上图,先打开一个被烘培过的场景,场景根节点为scene_root,被烘培的gameobjet是地板、一颗小草、带logo的立方体:
然后使用工具给场景里面的相关节点挂脚本存数据:
把场景根节点scene_root拖出来做成prefab,然后删掉场景里面的scene_root,再把scene_root.prefab拖进场景里面:
此时模型是丢失了光照数据的,原因截图已说明。接着运行场景:
模型的光照又回来啦,原因截图已说明。