关于动态加载光照贴图的尝试
为什么要去做这么一个内容,我也不清楚,只是知道项目当中可能使用动态光源很“浪费”,所以想着烘培一个光照贴图出来,然后在动态加载进去。
尝试过最简单的方法,是叠加场景的办法,这个确实可行,也能解决大场景分割的问题,但是解决不了动态加载的模型光照问题,所以我使用了动态加载光照贴图以及赋值到模型身上来实现这么一个效果。
首先,你得要有一个场景并且带着光照信息的,然后我们将这个场景的光照贴图内容打包成.unity3d的一个ab包,当然,你想要什么格式就用什么格式,无所谓。
接下来使用下面的代码:
[MenuItem("Tools/SaveSceneLightmap")]
private static void SaveLightmap()
{
string scene = SceneManager.GetActiveScene().name;
string assetsPath = "Assets/" + scene + ".bytes";
string record = Path.Combine(Application.dataPath, scene + ".bytes");
List<string> assetsNames = new List<string>();
assetsNames.Add(assetsPath);
EditorUtility.DisplayProgressBar("save lightmap", "正在生成lightmap的相关配置", 0.1f);
if (File.Exists(record))
{
File.Delete(record);
}
FileStream fs = new FileStream(record, FileMode.OpenOrCreate, FileAccess.Write);
BinaryWriter writer = new BinaryWriter(fs);
int cnt = LightmapSettings.lightmaps.Length;
writer.Write(cnt);
Texture2D[] lmColors = new Texture2D[cnt];
Texture2D[] lmDirs = new Texture2D[cnt];
for (int i = 0; i < cnt; i++)
{
lmColors[i] = LightmapSettings.lightmaps[i].lightmapColor;
lmDirs[i] = LightmapSettings.lightmaps[i].lightmapDir;
writer.Write(lmColors[i] == null ? "" : lmColors[i].name);
writer.Write(lmDirs[i] == null ? "" : lmDirs[i].name);
if (lmColors[i] != null)
{
assetsNames.Add(AssetDatabase.GetAssetPath(lmColors[i]));
}
if (lmDirs[i] != null)
{
assetsNames.Add(AssetDatabase.GetAssetPath(lmDirs[i]));
}
}
writer.Flush();
writer.Close();
fs.Close();
AssetDatabase.ImportAsset(assetsPath);
string ab = Application.dataPath;
ab = ab.Substring(0, ab.Length - ".assets".Length);
//m_SaveFilePath = ab + "/AB" + m_BuildVersion + "/";
string exportTargetPath = ab + "/SceneAB/";
if (!Directory.Exists(exportTargetPath))
{
Directory.CreateDirectory(exportTargetPath);
}
EditorUtility.DisplayProgressBar("save lightmap", "正在生成lightmap的相关配置", 0.4f);
List<AssetBundleBuild> list = new List<AssetBundleBuild>();
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = scene + "_Lightmap.unity3d";
build.assetNames = assetsNames.ToArray();
list.Add(build);
BuildPipeline.BuildAssetBundles(exportTargetPath, list.ToArray(),
BuildAssetBundleOptions.UncompressedAssetBundle,
EditorUserBuildSettings.activeBuildTarget);
EditorUtility.DisplayProgressBar("save lightmap", "正在生成lightmap的相关配置", 0.9f);
Debug.Log("save exit.");
EditorUtility.ClearProgressBar();
AssetDatabase.Refresh();
EditorUtility.OpenWithDefaultApp(exportTargetPath);
}
其他都是一些辅助内容,重点就在收集光照贴图信息的那么几行当中。
这几行就是我们这个工具的重点,它将我们场景当中的光照信息都存到了二进制文件当中,然后打成ab包供项目使用。
上面的仅仅是打包场景光照信息,那么对于预制的光照信息怎么办呢,不着急,我们下面说。
[MenuItem("Tools/SavePrefabLightmap")]
private static void SavePrefab()
{
string scene = SceneManager.GetActiveScene().name;
GameObject go = GameObject.Find(scene);
if (go == null)
{
Debug.LogError("please create not with scene name.");
return;
}
string record = Path.Combine(Application.streamingAssetsPath, "Scene/" + scene + ".bytes");
if (File.Exists(record))
{
File.Delete(record);
}
FileStream fs = new FileStream(record, FileMode.OpenOrCreate, FileAccess.Write);
BinaryWriter writer = new BinaryWriter(fs);
for (int index = 0; index < go.transform.childCount; index++)
{
GameObject child = go.transform.GetChild(index).gameObject;
UnityEngine.Object oj = PrefabUtility.GetCorrespondingObjectFromSource(child);
if (oj != null)
{
MeshRenderer[] meshRenderer = child.GetComponentsInChildren<MeshRenderer>();
if (meshRenderer == null || meshRenderer.Length < 1)
{
break;
}
writer.Write(oj.name);
writer.Write(meshRenderer.Length);
for (int i = 0; i < meshRenderer.Length; i++)
{
writer.Write(meshRenderer[i].name);
writer.Write(meshRenderer[i].lightmapIndex);
writer.Write(meshRenderer[i].lightmapScaleOffset.x);
writer.Write(meshRenderer[i].lightmapScaleOffset.y);
writer.Write(meshRenderer[i].lightmapScaleOffset.z);
writer.Write(meshRenderer[i].lightmapScaleOffset.w);
}
}
}
writer.Flush();
writer.Close();
fs.Close();
AssetDatabase.Refresh();
}
这个就是收集预制体对应的光照信息内容,同样是输出到二进制当中,跟场景不一样的是,场景的我们是打包成了ab包,而这个仅仅是以配置文件的形式存在。
上面两个内容,已经将我们的光照信息都存好了,接下来我们该说说怎么使用这个内容了。
首先是场景方面,我们打开场景之后,加载相对应的ab包,读取到里面的信息,加载相对应的光照贴图,形成光照信息,放到光照管理上。
private void LoadLightmapAB(string scene, object t)
{
AssetBundle ab = t as AssetBundle;
TextAsset text = ab.LoadAsset<TextAsset>(scene);
MemoryStream ms = new MemoryStream(text.bytes);
ms.Position = 0;
BinaryReader reader = new BinaryReader(ms);
int cnt = reader.ReadInt32();
string[] lmcolors = new string[cnt];
string[] lmdirs = new string[cnt];
LightmapData[] datas = new LightmapData[cnt];
for (int i = 0; i < cnt; i++)
{
lmcolors[i] = reader.ReadString();
lmdirs[i] = reader.ReadString();
LightmapData data = new LightmapData();
if (!string.IsNullOrEmpty(lmcolors[i]))
{
data.lightmapColor = ab.LoadAsset<Texture2D>(lmcolors[i]);
}
if (!string.IsNullOrEmpty(lmdirs[i]))
{
data.lightmapDir = ab.LoadAsset<Texture2D>(lmdirs[i]);
}
datas[i] = data;
}
reader.Close();
ms.Close();
LightmapSettings.lightmapsMode = LightmapsMode.NonDirectional;
LightmapSettings.lightmaps = datas;
}
上面我们能清楚的看到怎么样去设置我们的场景光照信息,其实最重要的就在最下面的两句话。
那么我们场景的处理好了,接下来就应该把预制里面的放进来了。
private void Start()
{
SceneObjectXmlBase sceneData = new SceneObjectXmlBase("animatortest");
ConfigurationManager.Instance.LoadBinaryConfig<SceneObjectXmlBase>(ref sceneData, false, true);
string name = this.gameObject.name;
if (name.LastIndexOf("Clone") > 0)
{
name = name.Substring(0, name.LastIndexOf("Clone") - 1);
}
Debug.Log(name);
SceneObjectXmlBase.PrefabMRInfo info = sceneData.GetInfoData(name);
if (info != null)
{
MeshRenderer[] renderers = this.gameObject.GetComponentsInChildren<MeshRenderer>();
for (int index = 0; index < renderers.Length; index++)
{
string cn = renderers[index].gameObject.name;
if (cn.LastIndexOf("Clone") > 0)
{
cn = cn.Substring(0, cn.LastIndexOf("Clone") - 1);
}
for (int i = 0; i < info.m_Childs.Count; i++)
{
if (info.m_Childs[i].m_NodeName == cn)
{
renderers[index].lightmapIndex = info.m_Childs[i].m_Index;
renderers[index].lightmapScaleOffset = info.m_Childs[i].m_OffectScale;
break;
}
}
}
}
}
终极简单,无非就是一个读取文件数据,设置光照贴图信息的一些操作。
可能有很多小伙伴都有一些疑问,为什么要这么做,而且这个代码,我只在windows平台测试通过了,在移动平台没有多做测试,大家有什么想沟通建议的也可以留言我们一起探讨进步,谢谢。