unity3d LightmapSettings.lightmaps

2015年01月22日 09:39 0 点赞 0 评论 更新于 2025-11-21 15:02

在 Unity3D 中,ScriptableObject 是一个非常实用的类,它可以帮助开发者将运行时用到的类的实例数据存储到文件中。

项目背景

风刀的项目是一款使用 Unity3D 引擎制作的 3D 页游。为了在光影效果和效率之间取得平衡,项目采用了光照贴图(Lightmap)技术,且 Unity3D 烘焙出来的光影效果十分出色。关于烘焙部分,Unity3D 的官方文档已有详细说明,本文不再赘述,直接进入核心内容。

项目摒弃了 Unity3D 的多场景开发方式,在程序实现上,整个游戏仅使用一个场景,这样做的目的是能够对资源管理进行更精细的控制。同时,项目也自行实现了一套关卡组织机制。光照贴图的动态载入主要涉及两个问题:光照贴图的 AssetBundle 处理和场景光照贴图的载入恢复。

光照贴图的 AssetBundle 处理思路

原始思路

将所有光照贴图打包成一个 AssetBundle(需记录所有贴图的名称),客户端下载该包后,将所有贴图加载到内存中,再设置到 LightmapData 里。

采用的思路

鉴于第一种方法较为复杂,风刀项目直接采用了第二种方式:将 LightmapData 里的信息通过 ScriptableObject 序列化为 Asset,然后对该 Asset 进行打包。

由于要求 ScriptableObject 序列化的类本身支持序列化,经测试,LightMapData 不支持序列化。不过,我们可以将纹理数据从 LightMapData 中取出进行序列化。以下是用于序列化 lightmapdata 的类:

public class LightMapAsset : ScriptableObject
{
public Texture2D[] lightmapFar;
public Texture2D[] lightmapNear;
}

制作 Asset 的代码实现

// 创建 LightMapAsset 实例
LightMapAsset lightmapAsset = ScriptableObject.CreateInstance<LightMapAsset>();
int count = LightmapSettings.lightmaps.Length;
lightmapAsset.lightmapFar = new Texture2D[count];
lightmapAsset.lightmapNear = new Texture2D[count];

for (int i = 0; i < count; i++)
{
// 存储光照贴图纹理
lightmapAsset.lightmapFar[i] = LightmapSettings.lightmaps[i].lightmapFar;
lightmapAsset.lightmapNear[i] = LightmapSettings.lightmaps[i].lightmapNear;
}

打包 Asset 的代码实现

// 创建 Asset
AssetDatabase.CreateAsset(lightmapAsset, "Assets/" + AssetName);
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath("Assets/" + AssetName, typeof(LightMapAsset));

string exportTargetPath = Application.dataPath + "/" + targetPath;
if (!File.Exists(exportTargetPath))
{
Directory.CreateDirectory(exportTargetPath);
}

string lightMapBundleName = SceneName + "_lightmap.bundle";
BuildPipeline.BuildAssetBundle(obj, null, exportTargetPath + lightMapBundleName.ToLower());

// 删除临时文件
AssetDatabase.DeleteAsset("Assets/" + AssetName);

游戏运行时恢复光照贴图数据

在游戏运行时恢复光照贴图数据相对简单,以下是风刀项目的测试代码片段:

if (info.www.assetBundle.mainAsset is LightMapAsset)
{
LightMapAsset lightmapAsset = info.www.assetBundle.mainAsset as LightMapAsset;
int count = lightmapAsset.lightmapFar.Length;
LightmapData[] lightmapDatas = new LightmapData[count];

for (int i = 0; i < count; ++i)
{
LightmapData lightmap = new LightmapData();
lightmap.lightmapFar = lightmapAsset.lightmapFar[i];
lightmap.lightmapNear = lightmapAsset.lightmapNear[i];
lightmapDatas[i] = lightmap;
}

LightmapSettings.lightmaps = lightmapDatas;
}

注意事项

在记录场景物件的信息时,需要记录 GameObjectlightmapIndexlightmapTilingOffset。当光照贴图数据从外部加载完成后,要恢复物件的 lightmapIndexlightmapTilingOffset。当光照贴图数据尚未加载完成时,要将 lightmapIndex 设置为 -1(不使用光照贴图),否则会出现渲染错误。