在游戏开发中,我们经常会遇到以下情况:
1,烘焙场景太大,每次只需要烘焙部分;
2,资源热更,需要分步加载;
3,场景跳转太慢,需手动处理资源;
4,网页游戏不方便场景跳转。
诸如此类问题,就需要将资源分配到不同的小场景分别进行烘焙,最后统一到综合场景中进行合并加载。
这里主要参考了这两篇博客:Unity在一个场景中使用其他场景烘焙的物体,Unity Lightmap使用总结。
其中第一篇博客提供了解决思路,但年代久远;第二篇不太完整,所以取舍之后做出如下解决方案。
将父节点及之下需烘焙的子节点勾选静态模式,自行设置参数进行烘焙:
烘焙会出现场景同名文件夹,如下图所示:
烘焙场景可以拷贝,这样能保持烘焙信息一致;另外拷贝一份删除所有信息,作为主场景。
1,主场景渲染管理器,这个主要是控制场景的光照贴图信息,所以要放到Awake中启动,并在需要的时候刷新:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LightMapControl : MonoBehaviour
{
[SerializeField]
public List _renderDatainfo = new List();
// Start is called before the first frame update
void Awake()
{
SetLightMapData();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
SetLightMapData();
}
}
///
/// 对场景进行烘培灯光性息的赋值\n
/// _renderDatainfo: LIST类型的贴图组
///
public void SetLightMapData()
{
// //缔特
// //整理灯光贴图
// //创建临时贴图list,并复制导入数据进入
LightmapSettings.lightmapsMode = LightmapsMode.NonDirectional;
List Templightmaps = new List(this._renderDatainfo.ToArray());
// this._renderDatainfo.ForEach(i => Templightmaps.Add(i));
// Templightmaps = this._renderDatainfo; //List
//遍历当前场景数据,如果临时list里有就删除list里的数据,并按当前id插入进去,得到最终贴图的顺序
for (int i = 0; i < LightmapSettings.lightmaps.Length; i++)
{
Texture2D nowTex = LightmapSettings.lightmaps[i].lightmapColor;
int ia = Templightmaps.IndexOf(nowTex);
if (ia != -1)
{
Templightmaps.RemoveAt(ia);
}
Templightmaps.Insert(i, LightmapSettings.lightmaps[i].lightmapColor);
}
//把贴图赋值给当前场景
List aa = new List();
Templightmaps.ForEach(i => aa.Add(this.setNewLightmapdata(i)));
LightmapSettings.lightmaps = aa.ToArray();
}
private LightmapData setNewLightmapdata(Texture2D a)
{
LightmapData tt = new LightmapData();
tt.lightmapColor = a;
return tt;
}
}
将该脚本拖到主场景中,并将各个分场景烘焙的贴图赋值到光照贴图数组中,运行结果如下:
2,记录保存和加载使用烘焙信息,这里需要将父节点设置为预制体:
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(LightmapRoot))]
public class LightmapRootEditor : Editor
{
private LightmapRoot Root
{
get { return target as LightmapRoot; }
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
GUILayout.Space(10);
if (GUILayout.Button("SaveLightmapData"))
{
Root.GetAllLightmapData();
SavePrefab();
}
GUILayout.Space(10);
if (GUILayout.Button("SetLightmapData"))
{
Root.SetAllLightmapData();
}
}
private void SavePrefab()
{
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(Root);
Debug.Log("记录预制体信息");
if (prefab != null)
{
PrefabUtility.ReplacePrefab(Root.gameObject, prefab);
Debug.Log(prefab);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LightMapControl : MonoBehaviour
{
[SerializeField]
public List _renderDatainfo = new List();
// Start is called before the first frame update
void Awake()
{
SetLightMapData();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
SetLightMapData();
}
}
///
/// 对场景进行烘培灯光性息的赋值\n
/// _renderDatainfo: LIST类型的贴图组
///
public void SetLightMapData()
{
// //缔特
// //整理灯光贴图
// //创建临时贴图list,并复制导入数据进入
LightmapSettings.lightmapsMode = LightmapsMode.NonDirectional;
List Templightmaps = new List(this._renderDatainfo.ToArray());
// this._renderDatainfo.ForEach(i => Templightmaps.Add(i));
// Templightmaps = this._renderDatainfo; //List
//遍历当前场景数据,如果临时list里有就删除list里的数据,并按当前id插入进去,得到最终贴图的顺序
for (int i = 0; i < LightmapSettings.lightmaps.Length; i++)
{
Texture2D nowTex = LightmapSettings.lightmaps[i].lightmapColor;
int ia = Templightmaps.IndexOf(nowTex);
if (ia != -1)
{
Templightmaps.RemoveAt(ia);
}
Templightmaps.Insert(i, LightmapSettings.lightmaps[i].lightmapColor);
}
//把贴图赋值给当前场景
List aa = new List();
Templightmaps.ForEach(i => aa.Add(this.setNewLightmapdata(i)));
LightmapSettings.lightmaps = aa.ToArray();
}
private LightmapData setNewLightmapdata(Texture2D a)
{
LightmapData tt = new LightmapData();
tt.lightmapColor = a;
return tt;
}
}
这里的具体原理可参考博客:Unity Lightmap使用总结
使用方法是将一个放到Editor文件夹中,另一个赋值给各个分场景的预制体父节点,以记录烘焙信息。使用结果如下:
这里的basic是记录光照贴图初始位置的,下标从0开始。
3,在主场景中测试
测试代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
for (var _i = 1; _i <= 3; _i++)
{
GameObject _obj = Instantiate(Resources.Load("" + _i));
}
}
// Update is called once per frame
void Update()
{
}
}
测试结果为:
测试工程连接为:https://download.csdn.net/download/Tel17610887670/14934894
这种方法在流程化的情况下,操作起来比较简单,但还是有如下不足之处:
1,因为静态的缘故,会占用双份内存;
2,加载操作比较麻烦,需要分步处理;
在公司内部项目使用的时候,使用了更好的替代方案,大致思路是:合并网格之后,使用shader的顶点信息记录本身贴图与光照贴图。在此就不做公开。