Unity项目运行时动态更新烘培的光照贴图

  • 动态更新烘培的光照贴图
    • 场景的物件没有发生变化(也就是说没有运行时加载在场景上的Prefab)
    • 场景的烘培贴图已经更新,但是有些物件prefab想运行时加载进来
  • 扩展
    • 关于AssetBundle
    • 原文链接

动态更新烘培的光照贴图

在以前的项目中,需要用到加载一个有光照贴图的预制体的功能。一般情况下,直接加载这类的预制体,是不会有光照贴图的信息的,加载出来的是一个灰色的模型。
为了这个功能,经过查找,找到雨松大神的一篇文章,经过项目测试,解决方法是有效的。
运行时更新烘培贴图分两种情况:

场景的物件没有发生变化(也就是说没有运行时加载在场景上的Prefab)

此时可以直接更换烘培贴图。

using UnityEngine;
/// <summary>
/// 直接更新烘培贴图-------------现不使用
/// </summary>
public class ReplaceBakingMap : MonoBehaviour
{//烘培烘培贴图1public Texture2D greenLightMap;//烘培贴图2public 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 };}}
}

场景的烘培贴图已经更新,但是有些物件prefab想运行时加载进来

这种情况下,如果直接Instance的话这个Prefab是没有烘培信息的(灰颜色的)。
如下代码挂在GameObject上。当场景烘培结束后,把他保存成prefab,运行的时候直接加载进来就行了。

#if UNITY_EDITOR
using UnityEditor;
using System.IO;
#endif
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
/// <summary>
/// 保存有烘培贴图的预制
/// </summary>
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;/// <summary>/// 光照贴图资源路径/// </summary>const string LIGHTMAP_RESOURCE_PATH = "Assets/Resources/Lightmaps/";/// <summary>/// 自己建立的光照贴图结构体-------用于记录/// </summary>[System.Serializable]struct Texture2D_Remap{public int originalLightmapIndex;public Texture2D originalLightmap;public Texture2D lightmap0;public Texture2D lightmap1;}/// <summary>/// 场景内所有的光照贴图的容器/// </summary>static List<Texture2D_Remap> sceneLightmaps = new List<Texture2D_Remap>();void Awake(){ApplyLightmaps(m_RendererInfo, m_Lightmaps, m_Lightmaps2);}/// <summary>/// 申请光照贴图,更新光照贴图数组(把更新的光照贴图数据加入总数组LightmapSettings.lightmaps)/// </summary>/// <param name="rendererInfo"></param>/// <param name="lightmaps"></param>/// <param name="lightmaps2"></param>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;}/// <summary>/// 更新RendererInfo数组/// </summary>/// <param name="infos"></param>/// <param name="arrayOffsetIndex"></param>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");}/// <summary>/// 根据scenePath读取.exr文件,并根据resourcePath创建.asset文件,将相应的信息存入容器sceneLightmaps/// </summary>/// <param name="scenePath"></param>/// <param name="resourcePath"></param>/// <param name="root"></param>/// <param name="rendererInfos"></param>/// <param name="lightmaps"></param>/// <param name="lightmaps2"></param>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);}}}/// <summary>/// 添加光照贴图-------返回其在容器中下标/// </summary>/// <param name="scenePath"></param>/// <param name="resourcePath"></param>/// <param name="originalLightmapIndex"></param>/// <param name="lightmap"></param>/// <param name="lightmap2"></param>/// <returns></returns>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;}/// <summary>/// 创建光照贴图并返回/// </summary>/// <param name="filename"></param>/// <param name="resourcePath"></param>/// <param name="originalLightmapIndex"></param>/// <param name="lightmap"></param>/// <returns></returns>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资源进行加载,由于这是讲光照贴图的信息保存在预制体的脚本上,而打包成AssetBundle资源后,其预制体上的脚本会丢失,所以,其功能就会消失。这里我初步认为,可以通过数据表格(xml/csv/json等)进行光照贴图的信息数据保存。由于项目中并未涉及,故我没有实际解决这个问题。只是记录一下这个思路,有需要的可以尝试后,交流一下。

原文链接

原文链接:Unity3D研究院之Unity5.x运行时动态更新烘培贴图(八十七)|雨松MOMO

Unity项目运行时动态更新光照贴图 | LightMap相关推荐

  1. .NET6运行时动态更新限流阈值

    自FireflySoft.RateLimit发布以来,帮助了不少需要在.net中进行限流处理的用户.前段时间有个开发者发了一个pull request,大意是Redis重启的时候Lua script会 ...

  2. [Unity脚本运行时更新]C#7.3新特性

    洪流学堂,让你快人几步!本文首发于洪流学堂微信公众号. 本文是该系列<Unity脚本运行时更新带来了什么?>的第8篇. 洪流学堂公众号回复runtime,获取本系列所有文章. Unity2 ...

  3. [Unity脚本运行时更新]C#4新特性

    洪流学堂,让你快人几步!本文首发于洪流学堂微信公众号. 本文是该系列<Unity脚本运行时更新带来了什么?>的第2篇. 洪流学堂公众号回复runtime,获取本系列所有文章. Unity2 ...

  4. Unity脚本运行时更新带来了什么?

    洪流学堂,让你快人几步!本文首发于洪流学堂微信公众号. 本文是该系列<Unity脚本运行时更新带来了什么?>的第一篇,后续会结合Unity实例介绍C# 4 - C# 6都带来了具体哪些变化 ...

  5. SAP UI5 应用开发教程之五十八 - 使用工厂方法在运行时动态创建不同类型的列表行项目控件试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  6. [Unity脚本运行时更新]C#7.2新特性

    洪流学堂,让你快人几步!本文首发于洪流学堂微信公众号. 本文是该系列<Unity脚本运行时更新带来了什么?>的第7篇. 洪流学堂公众号回复runtime,获取本系列所有文章. Unity2 ...

  7. [Unity脚本运行时更新]C#7.1新特性

    洪流学堂,让你快人几步!本文首发于洪流学堂微信公众号. 本文是该系列<Unity脚本运行时更新带来了什么?>的第6篇. 洪流学堂公众号回复runtime,获取本系列所有文章. Unity2 ...

  8. [Unity脚本运行时更新]C#7新特性

    洪流学堂,让你快人几步!本文首发于洪流学堂微信公众号. 本文是该系列<Unity脚本运行时更新带来了什么?>的第5篇. 洪流学堂公众号回复runtime,获取本系列所有文章. Unity2 ...

  9. [Unity脚本运行时更新]C#6新特性

    洪流学堂,让你快人几步!本文首发于洪流学堂微信公众号. 本文是该系列<Unity脚本运行时更新带来了什么?>的第4篇. 洪流学堂公众号回复runtime,获取本系列所有文章. Unity2 ...

最新文章

  1. Python 技术篇-通过管道命令获取cmd执行的结果,获取os.system()、subprocess.Popen()执行命令返回的结果
  2. Mysql之增加数据_INSERT INTO
  3. centos ping不通百度 ping不通外网
  4. php 2个经纬度之间的距离,php计算两个经纬度之间的距离
  5. 为效能而生,企业级敏捷研发管理工具PingCode正式发布!
  6. python验证码识别——前处理
  7. staticmethod自己定制
  8. 基于微信旅游景区购票小程序毕业设计毕设作品(5)开题报告答辩PPT
  9. 企业微信商户号是什么?如何开通?
  10. gdrive无限网盘挂载systemd文件
  11. android原生坐标系,经纬度查寻地图位置,坐标系在线互转
  12. [高通SDM450][Android9.0]CTA认证--去掉彩信、短信、通话功能
  13. 谭浩强c语言不讲位运算呢,谭浩强C语言教程第十二章-位运算.doc
  14. 传智播客 php系列,传智播客PHP 2015-JS高级系列视频教程 83集
  15. 利用MRT进行多年LAI数据(MOD15A2)拼接和投影转换
  16. 湖南多校对抗赛(2015.03.28) H SG Value
  17. Linux 基本管理命令(系统管理,用户管理,进程管理)
  18. 全能站群软件管理AI写文章定时发布
  19. Win7/Win8/8.1最方便安装方法:使用Nt6 HDD installer从主硬盘引导安装
  20. 10月11日关于煤炭的期货交易

热门文章

  1. FIIL CC PRO和南卡A2降噪蓝牙耳机哪个降噪效果好?降噪效果好的蓝牙耳机推荐
  2. Matlab匿名函数的使用
  3. 智慧能源管理系统开篇
  4. ideal导入第三方架包
  5. 建筑工程师职称申报条件
  6. 靠谱百度网盘目录管理系统安装图文教程
  7. 视频怎么转换成gif?
  8. 浙江大学远程教育计算机作业4,浙江大学远程教育_计算机基础_第4次作业_Excel知识题...
  9. 华为mate8电池虚电校正_mate8怎么电池校正?
  10. python键盘怎么输入双引号_python中怎么输入引号 -问答-阿里云开发者社区-阿里云...