在很多情况下,我们为了提高渲染效率,一般都会让美术同学在制作场景时,设置场景相关节点的lightmap static属性,提前给整个场景烘培出静态的光照贴图lightmap,烘培的数据保存在场景目录下的LightmapSnapshot文件中,主要包括的数据有:
lightmaps:烘培出的光照贴图数组;
gameobject uid:被烘培的gameobject的唯一标识;
renderer的lightmapIndex:被烘培的gameobject的renderer组件所指向的光照贴图数组的索引;
renderer的lightmapScaleOffset:被烘培的gameobject的renderer组件所指向的光照贴图用于采样的区域坐标和宽高;
这个文件目前没有相关api读写,如果你想烘培完场景之后,把场景里面的gameobjet抽出来做成prefab,等切换完场景之后再用于动态加载是不可行的,因为抽出来的prefab在Instantiate之后将会是一个新的gameobject,uid自然和LightmapSnapshot文件里面记录的不一样,导致找不到对应的光照数据而造成模型没光照变暗或渲染错乱。
还有一种比较常见的需求是,在游戏运行时,通过更换光照贴图数据,营造场景在不同时间或季节的光照氛围,例如白天和黑夜等。
so,就算场景烘培完之后,我们还是要“动”它。
做法大概是,既然LightmapSnapshot文件我们不能动,那就把上面提到的光照数据保存到我们可以控制的文件里面,例如prefab。
首先,给场景根节点挂一个自定义组件,用于保存烘培出的光照贴图数组和烘培模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class SceneLightMapSetting : MonoBehaviour {
public Texture2D []lightmapFar, lightmapNear;
public LightmapsMode mode;
public void SaveSettings()
{
mode = LightmapSettings.lightmapsMode;
lightmapFar = null;
lightmapNear = null;
if (LightmapSettings.lightmaps != null && LightmapSettings.lightmaps.Length > 0)
{
int l = LightmapSettings.lightmaps.Length;
lightmapFar = new Texture2D[l];
lightmapNear = new Texture2D[l];
for (int i = 0; i < l; i++)
{
lightmapFar[i] = LightmapSettings.lightmaps[i].lightmapFar;
lightmapNear[i] = LightmapSettings.lightmaps[i].lightmapNear;
}
}
RendererLightMapSetting[] savers = Transform.FindObjectsOfType<RendererLightMapSetting>();
foreach(RendererLightMapSetting s in savers)
{
s.SaveSettings();
}
}
public void LoadSettings()
{
LightmapSettings.lightmapsMode = mode;
int l1 = (lightmapFar == null) ? 0 : lightmapFar.Length;
int l2 = (lightmapNear == null) ? 0 : lightmapNear.Length;
int l = (l1 < l2) ? l2 : l1;
LightmapData[] lightmaps = null;
if (l > 0)
{
lightmaps = new LightmapData[l];
for (int i = 0; i < l; i++)
{
lightmaps[i] = new LightmapData();
if (i < l1)
lightmaps[i].lightmapFar = lightmapFar[i];
if (i < l2)
lightmaps[i].lightmapNear = lightmapNear[i];
}
LightmapSettings.lightmaps = lightmaps;
}
}
void OnEnable()
{
#if UNITY_EDITOR
UnityEditor.Lightmapping.completed += SaveSettings;
#endif
}
void OnDisable()
{
#if UNITY_EDITOR
UnityEditor.Lightmapping.completed -= SaveSettings;
#endif
}
void Awake () {
if(Application.isPlaying){
LoadSettings();
}
}
}
|
再给场景里面被烘培的gameobject挂一组件,用于保存光照贴图数组的索引和光照贴图的区域坐标和宽高数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class RendererLightMapSetting : MonoBehaviour {
public int lightmapIndex;
public Vector4 lightmapScaleOffset;
public void SaveSettings()
{
if(!IsLightMapGo(gameObject)){
return;
}
Renderer renderer = GetComponent<Renderer>();
lightmapIndex = renderer.lightmapIndex;
lightmapScaleOffset = renderer.lightmapScaleOffset;
}
public void LoadSettings()
{
if(!IsLightMapGo(gameObject)){
return;
}
Renderer renderer = GetComponent<Renderer>();
renderer.lightmapIndex = lightmapIndex;
renderer.lightmapScaleOffset = lightmapScaleOffset;
}
public static bool IsLightMapGo(GameObject go){
if(go == null){
return false;
}
Renderer renderer = go.GetComponent<Renderer>();
if(renderer == null){
return false;
}
return true;
}
void Awake () {
if (Application.isPlaying) {
LoadSettings ();
}
}
}
|
如果手动挂上面两个脚本的话,繁琐且容易出错,so,写工具接口,用工具扫场景挂脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System;
public class SceneTools
{
static string[] _sNeedAssetType = new string[1]{
".unity",
};
[MenuItem("场景相关/1. 保存场景光照贴图信息", false, 111)]
public static void SaveSceneMapLightSetting()
{
UnityEngine.Object[] selObjs = Selection.GetFiltered (typeof(UnityEngine.Object), SelectionMode.DeepAssets);
if (selObjs == null || selObjs.Length == 0) {
Debug.LogError ("请到\"Scenes\" 目录下选中需要保存场景光照贴图信息的场景!");
return;
}
string assetPath;
UnityEngine.Object assetObj = null;
GameObject gameObj = null;
SceneLightMapSetting slms = null;
RendererLightMapSetting rlms = null;
bool needSave = false;
for (int i = 0; i < selObjs.Length; ++i) {
needSave = false;
assetPath = AssetDatabase.GetAssetPath (selObjs [i]);
foreach (string extName in _sNeedAssetType) {
if (assetPath.EndsWith (extName)) {
needSave = true;
break;
}
}
if (!needSave)
continue;
assetObj = AssetDatabase.LoadAssetAtPath (assetPath, typeof(UnityEngine.Object)) as UnityEngine.Object;
EditorApplication.OpenScene (assetPath);
gameObj = GameObject.Find ("scene_root");
if (gameObj == null) {
Debug.LogError ("不合法的场景:场景没有scene_root根节点!!");
continue;
}
slms = gameObj.GetComponent<SceneLightMapSetting>();
if(slms == null){
slms = gameObj.AddComponent<SceneLightMapSetting>();
}
Renderer[] savers = Transform.FindObjectsOfType<Renderer>();
foreach(Renderer s in savers)
{
if(s.lightmapIndex != -1){
rlms = s.gameObject.GetComponent<RendererLightMapSetting>();
if(rlms == null){
rlms = s.gameObject.AddComponent<RendererLightMapSetting>();
}
}
}
slms.SaveSettings();
EditorApplication.SaveScene();
Debug.Log(string.Format("场景{0}的光照贴图信息保存完成", assetObj.name));
}
EditorApplication.SaveAssets();
}
}
|
没截图从觉得少点什么,来,上图,先打开一个被烘培过的场景,场景根节点为scene_root,被烘培的gameobjet是地板、一颗小草、带logo的立方体:
然后使用工具给场景里面的相关节点挂脚本存数据:
把场景根节点scene_root拖出来做成prefab,然后删掉场景里面的scene_root,再把scene_root.prefab拖进场景里面:
此时模型是丢失了光照数据的,原因截图已说明。接着运行场景:
模型的光照又回来啦,原因截图已说明。