在上一篇了解了光照以及光照贴图的一些相关属性后,在这篇具体讲讲相关的使用,以及自己预见的一些坑。
首先还是在上一篇的场景基础上,我们隐藏Realtime和Mixed光源,只留Baked的光源用来进行场景的烘焙。在Lighting设置中,只勾选Baked GI选项,Lighting Mode选择Subtractive。取消自动烘焙Auto Generate的勾选,点击Generate Lighting,手动生成Lightmap。同时Unity会在.scene文件同级目录下生成一个和scene名相同名称的文件夹,用于存放lightmap数据。相比自动生成的情况,在Baked Lightmaps中会多出一个Lighting Data Asset的参数,对应的LightingData.asset文件用于存放一些Lighting数据。
生成好之后运行Unity,修改Baked光源发现不影响场景的显示,一切都正常。
但是,当我们把静态场景存为Prefab的时候,就会出现一些问题了。而在实际的开发中,很多情况下会把静态的场景存为Prefab用于动态的加载。
我们将测试场景的物体放在同一个根目录下,然后将其转变为Prefab,点击运行。你会发现动态物体(胶囊体)也受到了baked光源的影响,变成了红色。关闭baked光源你会发现整个场景的物体都变黑了,baked的光源效果变成了realtime。
我们随便点击一个静态物体会发现其MeshRenderer中的Lightmapping属性关联的Lightmap不见了。造成这一现象的原因是,我们生成Lightmap的时候,其对应的信息都是和场景中GameObject的唯一ID相关联。当我们将这些GameObject变为Prefab时(包括后期加载该Prefab到场景中),唯一ID发生了变化,导致信息无法正确的关联起来。
有一个解决方案是,我们创建一个组件,挂载Prefab上。在烘焙完成后,这个组件会遍历该节点下所有静态物体的MeshRenderer,存储对应的Lightmap信息。当加载该Prefab的时候,将对应的Lightmap信息重新关联到对应的MeshRenderer上,代码如下:
注:由于很多时候美术可能会在一个新的场景中设置地图,然后会设置一些环境光,Fog等信息,我们程序在别的场景加载的时候,除了对应的地图Prefab外,这些信息也应该对应的设置,所以同Lightmap一起写在了脚本中。
注:由于Terrain比较特殊,如果地图中用到的话,需要单独读取信息。
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Rendering;
///
/// 渲染信息
///
[System.Serializable]
public struct RendererInfo
{
public Renderer renderer;
public int LightmapIndex;
public Vector4 LightmapOffsetScale;
}
[System.Serializable]
public class SettingProperty
{
[SerializeField]
public Material Skybox;
[SerializeField]
public LightmapsMode Mode;
[SerializeField]
public Texture2D[] LightmapsColor;
[SerializeField]
public Texture2D[] LightmapsDir;
[SerializeField]
public Texture2D[] ShadowMask;
[SerializeField]
public LightProbes Probes;
[SerializeField]
public Color AmbientColor;
[SerializeField]
public AmbientMode AmbientMode;
[SerializeField]
public Color SkyColor;
[SerializeField]
public Color EquatorColor;
[SerializeField]
public Color GroundColor;
[SerializeField]
public bool UseFog;
[SerializeField]
public Color FogColor;
[SerializeField]
public FogMode FogMode = FogMode.Linear;
[SerializeField]
public float FogDensity;
[SerializeField]
public float LinearFogStartDistance = 0;
[SerializeField]
public float LinearFogEndDistance = 300;
[SerializeField]
public RendererInfo[] RendererInfos;
[SerializeField]
public RendererInfo TerrainRendererInfo;
}
public class LightmapConfig: MonoBehaviour
{
static LightmapConfig _instance;
public static LightmapConfig sInstance { get { return _instance; } }
public SettingProperty Setting;
private SettingProperty OriginalSetting;
private static Stack sConfigStack = new Stack();
void Awake()
{
_instance = this;
SwichLightmapConfig();
sConfigStack.Push(this);
}
void OnDestroy()
{
if (sConfigStack.Count > 0)
{
sConfigStack.Pop();
if (sConfigStack.Count > 0)
{
_instance = sConfigStack.Peek();
_instance.SwichLightmapConfig();
}
}
}
public void StoreCurrentConfig()
{
Setting = new SettingProperty();
StoreSettings(ref Setting);
}
//读取信息
public virtual void StoreSettings(ref SettingProperty prop)
{
prop.Skybox = RenderSettings.skybox;
prop.AmbientColor = RenderSettings.ambientLight;
prop.AmbientMode = RenderSettings.ambientMode;
prop.SkyColor = RenderSettings.ambientSkyColor;
prop.EquatorColor = RenderSettings.ambientEquatorColor;
prop.GroundColor = RenderSettings.ambientGroundColor;
prop.UseFog = RenderSettings.fog;
prop.FogColor = RenderSettings.fogColor;
prop.FogMode = RenderSettings.fogMode;
prop.FogDensity = RenderSettings.fogDensity;
prop.LinearFogStartDistance = RenderSettings.fogStartDistance;
prop.LinearFogEndDistance = RenderSettings.fogEndDistance;
prop.LightmapsColor = new Texture2D[LightmapSettings.lightmaps.Length];
prop.LightmapsDir = new Texture2D[LightmapSettings.lightmaps.Length];
prop.ShadowMask = new Texture2D[LightmapSettings.lightmaps.Length];
for (int i = 0; i < LightmapSettings.lightmaps.Length; i++)
{
prop.LightmapsColor[i] = LightmapSettings.lightmaps[i].lightmapColor;
prop.LightmapsDir[i] = LightmapSettings.lightmaps[i].lightmapDir;
prop.ShadowMask[i] = LightmapSettings.lightmaps[i].shadowMask;
}
List renderers = new List();
MeshRenderer[] subRenderers = GetComponentsInChildren();
foreach (MeshRenderer meshRenderer in subRenderers)
{
if (meshRenderer.lightmapIndex == -1) continue;
RendererInfo renderInfo = new RendererInfo();
renderInfo.renderer = meshRenderer;
renderInfo.LightmapIndex = meshRenderer.lightmapIndex;
renderInfo.LightmapOffsetScale = meshRenderer.lightmapScaleOffset;
renderers.Add(renderInfo);
}
prop.RendererInfos = renderers.ToArray();
Terrain terrain = GetComponentInChildren();
if (terrain != null)
{
prop.TerrainRendererInfo = new RendererInfo();
prop.TerrainRendererInfo.LightmapIndex = terrain.lightmapIndex;
prop.TerrainRendererInfo.LightmapOffsetScale = terrain.lightmapScaleOffset;
}
}
//重新设置
public virtual void ApplyConfig(SettingProperty prop)
{
RenderSettings.skybox = prop.Skybox;
RenderSettings.ambientLight = prop.AmbientColor;
RenderSettings.fog = prop.UseFog;
RenderSettings.fogColor = prop.FogColor;
RenderSettings.fogMode = prop.FogMode;
RenderSettings.fogDensity = prop.FogDensity;
RenderSettings.fogStartDistance = prop.LinearFogStartDistance;
RenderSettings.fogEndDistance = prop.LinearFogEndDistance;
RenderSettings.ambientMode = prop.AmbientMode;
RenderSettings.ambientSkyColor = prop.SkyColor;
RenderSettings.ambientEquatorColor = prop.EquatorColor;
RenderSettings.ambientGroundColor = prop.GroundColor;
if (prop.LightmapsColor != null && prop.LightmapsColor.Length > 0)
{
LightmapData[] lds = new LightmapData[prop.LightmapsColor.Length];
for (int i = 0; i < prop.LightmapsColor.Length; i++)
{
lds[i] = new LightmapData();
lds[i].lightmapColor = prop.LightmapsColor[i];
lds[i].lightmapDir = prop.LightmapsDir[i];
lds[i].shadowMask = prop.ShadowMask[i];
}
LightmapSettings.lightmaps = lds;
}
else
{
LightmapSettings.lightmaps = null;
}
for (int i = 0; i < prop.RendererInfos.Length; i++)
{
RendererInfo info = prop.RendererInfos[i];
info.renderer.lightmapIndex = info.LightmapIndex;
info.renderer.lightmapScaleOffset = info.LightmapOffsetScale;
}
Terrain terrain = GetComponentInChildren();
if (terrain != null)
{
terrain.lightmapIndex = prop.TerrainRendererInfo.LightmapIndex;
terrain.lightmapScaleOffset = prop.TerrainRendererInfo.LightmapOffsetScale;
}
}
void RestoreLightmapConfig()
{
if (OriginalSetting != null)
{
ApplyConfig(OriginalSetting);
}
}
public void SwichLightmapConfig()
{
if (Setting != null)
{
OriginalSetting = new SettingProperty();
StoreSettings(ref OriginalSetting);
ApplyConfig(Setting);
}
}
}
然后我们在Editor目录下创建一个脚本,如下,功能是该组件在Inspector添加一个按钮,点击后保存信息。
using UnityEditor;
using UnityEngine;
using System.Collections;
[CustomEditor(typeof(LightmapConfig))]
public class LightmapConfigInspector : Editor
{
public override void OnInspectorGUI()
{
GUI.changed = false;
LightmapConfig config = target as LightmapConfig;
Undo.RecordObject(config, "ModifyLightmapConfig");
if(GUILayout.Button("Store Current Setting"))
{
config.StoreCurrentConfig();
}
base.OnInspectorGUI();
if (GUI.changed)
{
EditorUtility.SetDirty(config);
}
}
}
脚本搞好之后,我们在父节点上挂载LightmapConfig组件,然后重新烘焙后,点击Store Current Setting按钮,即可保存好Lightmap等信息。
然后运行Unity,会发现静态物体的Lightmap信息都关联上了。但是Baked的光源还是起到了realtime的效果,只有关闭了baked光源才正常。
针对烘焙光源全是Baked的话,我们可以在运行的时候取消这些光源,达到正确的效果。但是如果有mixed的光源,依旧还是会存在问题(即,mixed光源对静态物体进行烘焙,但是运行后还是产生影响,但是mixed光源不能像baked光源一样取消,因为它还需要对动态物体产生效果)
所以最终还是建议使用多场景的方式来处理。即在程序的Scene中,加载美术烘焙好的地图Scene(UnityEngine.SceneManagement.LoadSceneMode.Additive)
注:在Unity2018.2.3中,若不设置Map Scene为ActiveScene会出现花屏的现象,在2019中则正常。