在上一篇了解了光照以及光照贴图的一些相关属性后,在这篇具体讲讲相关的使用,以及自己预见的一些坑。

首先还是在上一篇的场景基础上,我们隐藏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 navigation 不跳出烘培面板 unity怎么取消烘焙_System

unity navigation 不跳出烘培面板 unity怎么取消烘焙_lightmap_02

unity navigation 不跳出烘培面板 unity怎么取消烘焙_LightingData_03

生成好之后运行Unity,修改Baked光源发现不影响场景的显示,一切都正常。

但是,当我们把静态场景存为Prefab的时候,就会出现一些问题了。而在实际的开发中,很多情况下会把静态的场景存为Prefab用于动态的加载。

我们将测试场景的物体放在同一个根目录下,然后将其转变为Prefab,点击运行。你会发现动态物体(胶囊体)也受到了baked光源的影响,变成了红色。关闭baked光源你会发现整个场景的物体都变黑了,baked的光源效果变成了realtime。

我们随便点击一个静态物体会发现其MeshRenderer中的Lightmapping属性关联的Lightmap不见了。造成这一现象的原因是,我们生成Lightmap的时候,其对应的信息都是和场景中GameObject的唯一ID相关联。当我们将这些GameObject变为Prefab时(包括后期加载该Prefab到场景中),唯一ID发生了变化,导致信息无法正确的关联起来。

unity navigation 不跳出烘培面板 unity怎么取消烘焙_unity_04

有一个解决方案是,我们创建一个组件,挂载Prefab上。在烘焙完成后,这个组件会遍历该节点下所有静态物体的MeshRenderer,存储对应的Lightmap信息。当加载该Prefab的时候,将对应的Lightmap信息重新关联到对应的MeshRenderer上,代码如下:

注:由于很多时候美术可能会在一个新的场景中设置地图,然后会设置一些环境光,Fog等信息,我们程序在别的场景加载的时候,除了对应的地图Prefab外,这些信息也应该对应的设置,所以同Lightmap一起写在了脚本中。

注:由于Terrain比较特殊,如果地图中用到的话,需要单独读取信息。

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Rendering;

/// <summary>
/// 渲染信息
/// </summary>
[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<LightmapConfig> sConfigStack = new Stack<LightmapConfig>();

    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<RendererInfo> renderers = new List<RendererInfo>();
        MeshRenderer[] subRenderers = GetComponentsInChildren<MeshRenderer>();

        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<Terrain>();
        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<Terrain>();
        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 navigation 不跳出烘培面板 unity怎么取消烘焙_LightingData_05

然后运行Unity,会发现静态物体的Lightmap信息都关联上了。但是Baked的光源还是起到了realtime的效果,只有关闭了baked光源才正常。

针对烘焙光源全是Baked的话,我们可以在运行的时候取消这些光源,达到正确的效果。但是如果有mixed的光源,依旧还是会存在问题(即,mixed光源对静态物体进行烘焙,但是运行后还是产生影响,但是mixed光源不能像baked光源一样取消,因为它还需要对动态物体产生效果)

所以最终还是建议使用多场景的方式来处理。即在程序的Scene中,加载美术烘焙好的地图Scene(UnityEngine.SceneManagement.LoadSceneMode.Additive)

注:在Unity2018.2.3中,若不设置Map Scene为ActiveScene会出现花屏的现象,在2019中则正常。