静态对象

静态对象是Unity提供的一个属性,它可以附加在游戏对象或者Prefab上。它的原理是限制物体在运行中不能发生位移变化,预先生成一些辅助数据,从而达成一种用内存换时间的优化方式。静态元素的种类很多。选择任意游戏对象,单击inspector右上角的Static下拉框,即可设置该对象的静态元素了,具体如下

  • Lightmap Static:这个勾选Static后会自动出现,用来表示接受烘焙光照计算,可烘焙光照贴图。
  • Occluder Static:表示自身是否可以遮挡其他元素。
  • Bathing Static:表示支持静态合批
  • Navigation Static:表示可烘焙寻路网格
  • Occludee Static:表示自身可以被遮挡剔除掉
  • Off Mesh Link Generation:寻路连接不同区域的点,就像角色从山顶跳下来?
  • Reflection Probe Static:反射探头(Probe),就像玻璃反射一样的镜面效果。

静态对象

Lightmap设置烘焙贴图

lightmap技术的原理是将场景中的灯光与物体产生的光照与阴影信息烘焙在一张或者多张Lightmap贴图中,这些物体将不再参与实时光照计算,从而减少了大量的性能开销。它的缺点就是参与烘焙计算的对象在游戏过程中不能发生移动。游戏中要将物体分成两类,一类是可发生位移变化的,它们使用实时光照计算;另一类是不可发生位移变化的,它们预烘焙Lightmap。

设置烘焙贴图

首先,需要在场景中需要参与烘焙计算的游戏对象,就会出现烘焙信息参数

设置Lightmapping参数

当烘焙对象都设置完毕后,从WIndow——Rendering——Lighting即可打开烘焙面板。设置完成参数后,单击右下角的Bake Reflection Probes命令,即可开始烘焙。如果选中了Auto Generate复选框,将会自动烘焙,复杂场景情况下开启此选项会导致卡顿。

烘焙面板

渐进光照贴图程序 - Unity 手册 (unity3d.com)

Unity渐进式光照贴图烘焙(Progressive Lightmapper)详 - 技术专栏 - Unity官方开发者社区

实时光和烘焙光共存

在游戏中,少部分物体需要实时光照,例如控制主角移动时,动态地产生光照和阴影信息。

我们在一个灯光的inspector面板中可以设置Mode,Mixed表示实时光和烘焙光的混合模式,游戏中更多的会使用这种模式

光照模式

光照模式 - Unity 手册 (unity3d.com)

灯光管理

游戏后期光源很多。在Window——Rendering——Light Explorer可以打开灯光管理菜单,点击后可以设置开关、灯光类型和模式,并且会自动在Scene视图中定位

灯光管理

运行时更换烘焙贴图

游戏中有一个白天场景和夜晚场景,就需要烘焙多张烘焙贴图。在运行时,可以动态更换白天和夜晚的烘焙贴图。

首先创建LightmapData对象,最终将需要更换的烘焙贴图放入LightmapSettings.lightmaps中即可。

新建ExchangingLightmap脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine;

public class ExchangingLightmap : MonoBehaviour
{
public Texture2D lightmap1;
public Texture2D lightmap2;
private void OnGUI()
{
if(GUI.Button(new Rect(100f, 200f, 150f, 50f), "ligntmap1"))
{
LightmapData data = new LightmapData();
data.lightmapColor = lightmap1;
LightmapSettings.lightmaps = new LightmapData[1] { data };
}
if (GUI.Button(new Rect(300f, 200f, 150f, 50f), "ligntmap2"))
{
LightmapData data = new LightmapData();
data.lightmapColor = lightmap2;
LightmapSettings.lightmaps = new LightmapData[1] { data };
}
}
}

这里使用的Texture2D是提前烘焙好的完整lightmap贴图,在Unity使用新版Progressive烘焙系统后,烘焙出来的不是单张lightmap了

动态更换游戏对象

光照和阴影信息都记录在烘焙贴图上,但是如果需要动态地加载prefab,此prefab的烘焙信息就会丢失,此时可以给它绑定一个脚本,在生成Prefab的同时将烘焙信息写入这个脚本中,以便在实例化Prefab时再将信息写入。

新建PrefabLightmap脚本

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class PrefabLightmap : MonoBehaviour
{
public int lightmapIndex;
public Vector4 lightmapScaleOffset;

private void Awake()
{
//Prefab实例化后赋值
Renderer renderer = GetComponent<Renderer>();
if (renderer)
{
renderer.lightmapIndex = lightmapIndex;
renderer.lightmapScaleOffset = lightmapScaleOffset;
}
}
#if UNITY_EDITOR
[MenuItem("GameObject/Light/ToPrefab")]
static void ToPrefab()
{
//确保选择Hierarchy视图下的一个游戏对象
if (Selection.activeTransform)
{
Renderer renderer = Selection.activeTransform.GetComponent<Renderer>();
//确保有Renderer组件
if (renderer)
{
PrefabLightmap prefabLightmap = Selection.activeTransform.GetComponent<PrefabLightmap>();
if (!prefabLightmap)
prefabLightmap = Selection.activeTransform.gameObject.AddComponent<PrefabLightmap>();
prefabLightmap.lightmapIndex = renderer.lightmapIndex;
prefabLightmap.lightmapScaleOffset = renderer.lightmapScaleOffset;

GameObject prefab = PrefabUtility.GetCorrespondingObjectFromSource(renderer.gameObject);
//如果有Prefab文件,就更新,没有就创建新的
if (prefab)
PrefabUtility.SaveAsPrefabAsset(prefab, string.Format("Assets/Resources/{0}.prefab", Selection.activeTransform.name));
else
PrefabUtility.SaveAsPrefabAsset(Selection.activeGameObject, string.Format("Assets/Resources/{0}.prefab", Selection.activeTransform.name));

}
}
}
#endif
}

从这个脚本可以看到,更换烘焙贴图就是设置正确的LightmapIndexLightmapScaleOffset

当场景烘焙完后,选择任意游戏对象(静态的),右键——Light——ToPrefab。我们上面的代码会自动判断此对象是否已经生成Prefab,并且挂载PrefabLightmap用来记录LightmapIndexLightmapScaleOffset,当我们实例化这个prefab时,记录的LightmapIndexLightmapScaleOffset就会重新赋值给Renderer

通过下面的脚本可以测试

1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;

public class TestLightmapPrefabScript : MonoBehaviour
{
public GameObject prefab;
private void OnGUI()
{
if (GUILayout.Button("<size=50>Load</size>"))
{
GameObject.Instantiate<GameObject>(prefab);
}
}
}

复制游戏对象

光照和阴影信息场景烘焙完毕后,静态物体再Ctrl+D出来的新物体并不能携带烘焙信息。我们可以自定义一个新的快捷键Ctrl+Shift+D来执行复制游戏对象并动态赋予烘焙信息的操作,这样免去一些重复烘焙的麻烦,但是注意物体产生的阴影信息是无法复制的。

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
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class DuplicatewithLightmap
{
[MenuItem("Root/DuplicateGameObject %#d")]
static void DuplicateGameObject()
{
if (Selection.activeTransform)
{
Dictionary<string, Renderer> save = new Dictionary<string, Renderer>();
//根据相对路径保存Renderer信息
foreach (var renderer in Selection.activeTransform.GetComponentsInChildren<Renderer>())
{
string path = AnimationUtility.CalculateTransformPath(renderer.transform, Selection.activeTransform);
save[path] = renderer;
}
//执行复制
EditorApplication.ExecuteMenuItem("Edit/Duplicate");
//还原烘焙信息
foreach (var renderer in Selection.activeTransform.GetComponentsInChildren<Renderer>())
{
string path = AnimationUtility.CalculateTransformPath(renderer.transform, Selection.activeTransform);
if (save.ContainsKey(path))
{
renderer.lightmapIndex = save[path].lightmapIndex;
renderer.lightmapScaleOffset = save[path].lightmapScaleOffset;
}
}
}
}
}

有关自定义快捷键,在Unity游戏开发拓展编辑器2——MenuItem菜单——拓展全局自定义按键

AnimationUtility.CalculateTransformPath(Transform targetTransform, Transform root):检索从根变换到目标变换的路径。

自定义复制