在以前的项目中,需要用到加载一个有光照贴图的预制体的功能。一般情况下,直接加载这类的预制体,是不会有光照贴图的信息的,加载出来的是一个灰色的模型。
为了这个功能,经过查找,找到雨松大神的一篇文章,经过项目测试,解决方法是有效的。
运行时更新烘培贴图分两种情况:
此时可以直接更换烘培贴图。
using UnityEngine;
///
/// 直接更新烘培贴图-------------现不使用
///
public class ReplaceBakingMap : MonoBehaviour
{
//烘培烘培贴图1
public Texture2D greenLightMap;
//烘培贴图2
public Texture2D redLightMap;
void OnGUI()
{
if (GUILayout.Button("green"))
{
LightmapData data = new LightmapData();
data.lightmapLight = greenLightMap;
LightmapSettings.lightmaps = new LightmapData[1] { data };
}
if (GUILayout.Button("red"))
{
LightmapData data = new LightmapData();
data.lightmapLight = redLightMap;
LightmapSettings.lightmaps = new LightmapData[1] { data };
}
}
}
这种情况下,如果直接Instance
的话这个Prefab是没有烘培信息的(灰颜色的)。
如下代码挂在GameObject上。当场景烘培结束后,把他保存成prefab,运行的时候直接加载进来就行了。
#if UNITY_EDITOR
using UnityEditor;
using System.IO;
#endif
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
///
/// 保存有烘培贴图的预制
///
public class SaveBakingMapOfPrefab : MonoBehaviour
{
[System.Serializable]
struct RendererInfo
{
public Renderer renderer;
public int lightmapIndex;
public Vector4 lightmapOffsetScale;
}
[SerializeField]
RendererInfo[] m_RendererInfo;
[SerializeField]
Texture2D[] m_Lightmaps;
[SerializeField]
Texture2D[] m_Lightmaps2;
///
/// 光照贴图资源路径
///
const string LIGHTMAP_RESOURCE_PATH = "Assets/Resources/Lightmaps/";
///
/// 自己建立的光照贴图结构体-------用于记录
///
[System.Serializable]
struct Texture2D_Remap
{
public int originalLightmapIndex;
public Texture2D originalLightmap;
public Texture2D lightmap0;
public Texture2D lightmap1;
}
///
/// 场景内所有的光照贴图的容器
///
static List<Texture2D_Remap> sceneLightmaps = new List<Texture2D_Remap>();
void Awake()
{
ApplyLightmaps(m_RendererInfo, m_Lightmaps, m_Lightmaps2);
}
///
/// 申请光照贴图,更新光照贴图数组(把更新的光照贴图数据加入总数组LightmapSettings.lightmaps)
///
///
///
///
static void ApplyLightmaps(RendererInfo[] rendererInfo, Texture2D[] lightmaps, Texture2D[] lightmaps2)
{
//是否已经存在
bool existsAlready = false;
//记录总数组中有几个光照贴图漏掉了
int counter = 0;
int[] lightmapArrayOffsetIndex;
//如果没有就返回
if (rendererInfo == null || rendererInfo.Length == 0)
return;
//场景中光照贴图的数组
var settingslightmaps = LightmapSettings.lightmaps;
//临时数组------存放不在场景总数组中的光照贴图
var combinedLightmaps = new List<LightmapData>();
//记录下标数组
lightmapArrayOffsetIndex = new int[lightmaps.Length];
for (int i = 0; i < lightmaps.Length; i++)
{
existsAlready = false;
for (int j = 0; j < settingslightmaps.Length; j++)
{//判断该贴图是否在所有光照贴图的数组中
if (lightmaps[i] == settingslightmaps[j].lightmapLight)
{
lightmapArrayOffsetIndex[i] = j;
existsAlready = true;
}
}
if (!existsAlready)
{//如果不在其中,则先记录下应该在总数组中的下标位置,再创建一个光照贴图数据,存放在临时数组中
lightmapArrayOffsetIndex[i] = counter + settingslightmaps.Length;
var newLightmapData = new LightmapData();
newLightmapData.lightmapLight = lightmaps[i];
newLightmapData.lightmapDir = lightmaps2[i];
combinedLightmaps.Add(newLightmapData);
++counter;
}
}
//建立一个临时的数组存放所有的光照贴图数据
var combinedLightmaps2 = new LightmapData[settingslightmaps.Length + counter];
//将settingslightmaps数组复制到combinedLightmaps2数组,并从下标0开始
settingslightmaps.CopyTo(combinedLightmaps2, 0);
if (counter > 0)
{
for (int i = 0; i < combinedLightmaps.Count; i++)
{
combinedLightmaps2[i + settingslightmaps.Length] = new LightmapData();
combinedLightmaps2[i + settingslightmaps.Length].lightmapLight = combinedLightmaps[i].lightmapLight;
combinedLightmaps2[i + settingslightmaps.Length].lightmapDir = combinedLightmaps[i].lightmapDir;
}
}
ApplyRendererInfo(rendererInfo, lightmapArrayOffsetIndex);
//将总数组更新
LightmapSettings.lightmaps = combinedLightmaps2;
}
///
/// 更新RendererInfo数组
///
///
///
static void ApplyRendererInfo(RendererInfo[] infos, int[] arrayOffsetIndex)
{
for (int i = 0; i < infos.Length; i++)
{
var info = infos[i];
info.renderer.lightmapIndex = arrayOffsetIndex[info.lightmapIndex];
info.renderer.lightmapScaleOffset = info.lightmapOffsetScale;
}
}
#if UNITY_EDITOR
[MenuItem("Assets/Update Scene with Prefab Lightmaps")]
static void UpdateLightmaps()
{//更新保存的光照贴图
SaveBakingMapOfPrefab[] prefabs = FindObjectsOfType<SaveBakingMapOfPrefab>();
foreach (var instance in prefabs)
{
ApplyLightmaps(instance.m_RendererInfo, instance.m_Lightmaps, instance.m_Lightmaps2);
}
Debug.Log("Prefab lightmaps updated");
}
[MenuItem("Assets/Bake Prefab Lightmaps")]
static void GenerateLightmapInfo()
{
Debug.ClearDeveloperConsole();
if (Lightmapping.giWorkflowMode != Lightmapping.GIWorkflowMode.OnDemand)
{//运行lightmapping只有当用户按下烘焙按钮时;如果不是则报错。
Debug.LogError("ExtractLightmapData requires that you have baked you lightmaps and Auto mode is disabled.");
return;
}
//开始烘培光照贴图
Lightmapping.Bake();
//光照贴图的保存路径----------Directory.GetCurrentDirectory()当前的目录
string lightMapPath = Path.Combine(Directory.GetCurrentDirectory(), LIGHTMAP_RESOURCE_PATH);
if (!Directory.Exists(lightMapPath))//如果路径不存在,则创建这个路径
Directory.CreateDirectory(lightMapPath);
sceneLightmaps = new List<Texture2D_Remap>();
//当前场景的路径
var scene = SceneManager.GetActiveScene().path;//因为Unity版本不同也可以使用EditorApplication.currentScene
//提炼出场景名
var sceneName = Path.GetFileNameWithoutExtension(scene);
//光照贴图资源(.asset)在Resources所要保存的路径
var resourcePath = LIGHTMAP_RESOURCE_PATH + sceneName;
//光照贴图16bit表示的光照数据文件(.exr)所保存的路径
var scenePath = Path.GetDirectoryName(scene) + "/" + sceneName + "/";
//找到所有在运行中的SaveBakingMapOfPrefab对象
SaveBakingMapOfPrefab[] prefabs = FindObjectsOfType<SaveBakingMapOfPrefab>();
foreach (var instance in prefabs)
{
var gameObject = instance.gameObject;
var rendererInfos = new List<RendererInfo>();
var lightmaps = new List<Texture2D>();
var lightmaps2 = new List<Texture2D>();
//为使方便在场景名的后面加上预制体的名字
resourcePath = resourcePath + "_" + gameObject.name;
//创建光照贴图资源
GenerateLightmapInfo(scenePath, resourcePath, gameObject, rendererInfos, lightmaps, lightmaps2);
instance.m_RendererInfo = rendererInfos.ToArray();
instance.m_Lightmaps = lightmaps.ToArray();
instance.m_Lightmaps2 = lightmaps2.ToArray();
//更改预制
var targetPrefab = PrefabUtility.GetPrefabParent(gameObject) as GameObject;//获取与本物体相关的预制
if (targetPrefab != null)
{//替换之前的预制层次
PrefabUtility.ReplacePrefab(gameObject, targetPrefab);
}
ApplyLightmaps(instance.m_RendererInfo, instance.m_Lightmaps, instance.m_Lightmaps2);
}
Debug.Log("Update to prefab lightmaps finished");
}
///
/// 根据scenePath读取.exr文件,并根据resourcePath创建.asset文件,将相应的信息存入容器sceneLightmaps
///
///
///
///
///
///
///
static void GenerateLightmapInfo(string scenePath, string resourcePath, GameObject root, List<RendererInfo> rendererInfos, List<Texture2D> lightmaps, List<Texture2D> lightmaps2)
{
var renderers = root.GetComponentsInChildren<MeshRenderer>();//光照贴图与MeshRenderer有关
foreach (MeshRenderer renderer in renderers)
{
if (renderer.lightmapIndex != -1)//申请的光照贴图序列号
{
RendererInfo info = new RendererInfo();
info.renderer = renderer;
//用于光照贴图的UV缩放和偏移量
info.lightmapOffsetScale = renderer.lightmapScaleOffset;
//分别获取该MeshRenderer,远与近的光照贴图----------LightmapSettings场景中光照贴图的容器;LightmapSettings.lightmaps场景中光照贴图的数组
Texture2D lightmap = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapLight;
Texture2D lightmap2 = LightmapSettings.lightmaps[renderer.lightmapIndex].lightmapDir;
int sceneLightmapIndex = AddLightmap(scenePath, resourcePath, renderer.lightmapIndex, lightmap, lightmap2);
//查找对象在链表中的下标为多少。-1为没有
info.lightmapIndex = lightmaps.IndexOf(sceneLightmaps[sceneLightmapIndex].lightmap0);
if (info.lightmapIndex == -1)
{
info.lightmapIndex = lightmaps.Count;
lightmaps.Add(sceneLightmaps[sceneLightmapIndex].lightmap0);
lightmaps2.Add(sceneLightmaps[sceneLightmapIndex].lightmap1);
}
rendererInfos.Add(info);
}
}
}
///
/// 添加光照贴图-------返回其在容器中下标
///
///
///
///
///
///
///
static int AddLightmap(string scenePath, string resourcePath, int originalLightmapIndex, Texture2D lightmap, Texture2D lightmap2)
{
int newIndex = -1;
//查找自己的光照贴图链表中是否存在想要加入的光照贴图,如果有则返回其下标
for (int i = 0; i < sceneLightmaps.Count; i++)
{
if (sceneLightmaps[i].originalLightmapIndex == originalLightmapIndex)
{
return i;
}
}
if (newIndex == -1)
{
//创建一个自己建立的光照贴图结构体对象
var lightmap_Remap = new Texture2D_Remap();
lightmap_Remap.originalLightmapIndex = originalLightmapIndex;
lightmap_Remap.originalLightmap = lightmap;
//组建文件名
var filename = scenePath + "Lightmap-" + originalLightmapIndex;
lightmap_Remap.lightmap0 = GetLightmapAsset(filename + "_comp_light.exr", resourcePath + "_light", originalLightmapIndex, lightmap);
if (lightmap2 != null)
{
lightmap_Remap.lightmap1 = GetLightmapAsset(filename + "_comp_dir.exr", resourcePath + "_dir", originalLightmapIndex, lightmap2);
}
//将新建的这个光照贴图结构体对象加入容器中
sceneLightmaps.Add(lightmap_Remap);
newIndex = sceneLightmaps.Count - 1;
}
//返回其下标
return newIndex;
}
///
/// 创建光照贴图并返回
///
///
///
///
///
///
static Texture2D GetLightmapAsset(string filename, string resourcePath, int originalLightmapIndex, Texture2D lightmap)
{
//导入一个资源------------filename路径;ImportAssetOptions导入资源的选择;ImportAssetOptions.ForceUpdate玩家主导的资源导入
AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
//检索资源导入的路径并返回一个对象,将这个对象强转(确定)为图片导入
var importer = AssetImporter.GetAtPath(filename) as TextureImporter;
//将已确定为图片导入的对象设置为脚本可读的----开始修改
importer.isReadable = true;
AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
//根据路径读取一个图片资源
var assetLightmap = AssetDatabase.LoadAssetAtPath<Texture2D>(filename);
//在Resources中的路径与文件名.扩展名
var assetPath = resourcePath + "-" + originalLightmapIndex + ".asset";
//将图片资源实例化
var newLightmap = Instantiate<Texture2D>(assetLightmap);
//根据路径创建光照贴图资源
AssetDatabase.CreateAsset(newLightmap, assetPath);
//根据路径读取这个新的图片资源
newLightmap = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
//将已确定为图片导入的对象设置为脚本不读的----停止修改
importer.isReadable = false;
AssetDatabase.ImportAsset(filename, ImportAssetOptions.ForceUpdate);
//返回这个新的图片资源
return newLightmap;
}
#endif
}
点击Assets/Bake Prefab Lightmaps进行烘培, 这样它的脚本里会把index和 offset保存在prefab里。它还会保存上当前烘培场景的Lightmap,如果运行时想更换的话,你可以加一些自己的逻辑进行切换。
该解决方法,在我的项目中,亲测时可用的。上面的代码都是我项目中用到的,与雨松大神的代码是一样的,但我做了一些注释,方便理解。
同时,这个方案是有一定局限的,仍需扩展。我项目所使用的Unity版本是5.3.5。
将于预制体打包成AssetBundle资源进行加载,由于这是讲光照贴图的信息保存在预制体的脚本上,而打包成AssetBundle资源后,其预制体上的脚本会丢失,所以,其功能就会消失。这里我初步认为,可以通过数据表格(xml/csv/json等)进行光照贴图的信息数据保存。由于项目中并未涉及,故我没有实际解决这个问题。只是记录一下这个思路,有需要的可以尝试后,交流一下。
原文链接:Unity3D研究院之Unity5.x运行时动态更新烘培贴图(八十七)|雨松MOMO