[Unity 3d] 实现黑夜/白天一键切换全攻略

在本文,笔者讲简单的讲一讲怎么在 Unity 中实现黑夜白天的一键切换。

演示:

场景中的灯效,除了直射光其他均是烘焙出来的。


色彩太丰富,实在不敢录太长时间

原理:

如果想要实现上面的这个黑夜白天切换效果,控制好以下几点就行了:

  1. 保证烘焙黑夜和白天光照贴图使用的是同一个场景(Ctrl+D挺好用)。
  2. 动态切换 黑夜和白天的 光照贴图设置(LightmapSettings)。
  3. 动态修改天空盒白天和黑夜对应的天空盒,或者像上面演示的那样:仅仅修改天空盒曝光值。
  4. 修改直射光的强度(如果有必要,可以修改直射光的角度)。
  5. 如果像演示中的镜面建筑,加了光照探针(ReflectionProbe)的需要指定对应的烘焙OK的贴图。
  6. 关于黑夜白天差异性的游戏对象:
    • 不参与烘焙的,比如上面的效果晚上多加的 Lens 辉光,直接白天显示隐藏晚上显示即可。
    • 参与烘焙的,可以直接使用显示 / 隐藏操作。
    • 参与烘焙的,也可以存为预制体动态加载,但该方案要写点代码保证游戏对象上烘焙的光照信息能被加载出来。

示例:

using System.Linq;
using UnityEngine;
using DG.Tweening;

public class TestSwitchBakedLightMap : MonoBehaviour
{
    [Header("Common Configuration and Component ")]
    public ReflectionProbe reflectionProbe;
    public Light directLight;
    public GameObject effectLights; //晚上要打灯,白天不打灯

    [Header("Configuration and Component For Day"), Space(10)]
    public LightDataConfiguration dayConfig;
    public Cubemap dayReflect;
    public float daySkyboxExposure = 1f;
    public float directLightDayIntensity = 1;
    public float dayLightIntensityMultiplier = 1;

    [Header("Configuration and Component For Night"), Space(10)]
    public LightDataConfiguration nightConfig;
    public Cubemap nightReflect;
    public float nightSkyboxExposure = 0.2f;
    public float directLightNightIntensity = 0.2f;
    public float nightLightIntensityMultiplier = 0.3f;

    // private fields
    LightmapData[] day;
    LightmapData[] night;
    bool isDay = true;

    private void Start()
    {
        night = nightConfig.Lightmaps
            .Select(x => new LightmapData() { lightmapColor = x.lightmapColor, shadowMask = x.shadowMask })
            .ToArray();
        day = dayConfig.Lightmaps
            .Select(x => new LightmapData() { lightmapColor = x.lightmapColor, shadowMask = x.shadowMask })
            .ToArray();
        RenderSettings.skybox.SetFloat("_Exposure", daySkyboxExposure);
        reflectionProbe.customBakedTexture = dayReflect;
    }

    void OnGUI()
    { 
        if (GUILayout.Button(isDay ? "To Night" : "To Day"))
        {
            SwithDayAndNight();
        }
    }

    public void SwithDayAndNight()
    {
        if (!isDay)
        {
            // 切换为白天的配置
            LightmapSettings.lightmaps = day;
            directLight.DOIntensity(directLightDayIntensity, 0.5f);
            DOTween.To(()=> RenderSettings.ambientIntensity, y=> { RenderSettings.ambientIntensity = y; },dayLightIntensityMultiplier,0.5f);
            RenderSettings.skybox.DOFloat(daySkyboxExposure, "_Exposure", 0.5f);
            effectLights.SetActive(false);
            reflectionProbe.mode = UnityEngine.Rendering.ReflectionProbeMode.Custom;
            reflectionProbe.customBakedTexture = dayReflect;
        }
        else
        {
            // 切换为晚上的配置
            LightmapSettings.lightmaps = night;
            directLight.DOIntensity(directLightNightIntensity, 0.5f);
            DOTween.To(()=> RenderSettings.ambientIntensity, y=> { RenderSettings.ambientIntensity = y; },nightLightIntensityMultiplier,0.5f);
            RenderSettings.skybox.DOFloat(nightSkyboxExposure, "_Exposure", 0.5f);
            effectLights.SetActive(true);
            reflectionProbe.customBakedTexture = nightReflect;
        }
        isDay = !isDay;
    }
}

Tips:

  • 仅仅是将原理中提到的几点找到对应的 API 通过代码动态修改罢了,仅供熟悉API.
  • 使用了 DoTween 的非扩展方法形式的缓动,可以熟悉下。
  • 使用了 DoTween 的扩展方法形式对天空盒(材质球)的曝光值进行了缓动。
  • 使用了 ScriptableObject 分别保存的黑夜/白天的光照贴图设置信息,只需要 lightmapColor 和 shadowMask 两组信息,使用 ScriptableObject 在本示例中的好处可要好好体会体会哈~
  • 演示中没有出现原理中第六条第三点提到的问题,所以示例代码没做演示。

扩展阅读:

  1. Unity5.x场景优化之动态设置光照贴图lightmap - yuyingwin的专栏 - CSDN博客
    ↑怎么让动态加载的游戏对象实例化后自动加载光照贴图效果:↑

  2. [Unity3D]Lightmapping使用及动态加载lightmap方案 - bread's code - CSDN博客
    ↑更多关于光照贴图+光照探针的参考资料↑

转载请注明出处,谢谢!

LightDataConfigurationEditor

using Malee;
using System;
using UnityEngine;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(LightDataConfiguration))]
public class LightDataConfigurationEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        if (GUILayout.Button("StoreCurrentLightData"))
        {
            (target as LightDataConfiguration).StoreCurrentLightmaps();
            EditorUtility.SetDirty(target);
        }
    }
}
#endif
/// <summary>
/// 抓取当前的灯光贴图
/// </summary>
[CreateAssetMenu(fileName = "LightDataConfiguration", menuName = "Configration/LightDataConfiguration")]
public class LightDataConfiguration : ScriptableObject
{
    [Reorderable, SerializeField]
    public LightMapInfoArray Lightmaps;
    public string label = "Solid";
    public bool forceOverride = false;

    public void StoreCurrentLightmaps()
    {
        if (string.IsNullOrEmpty(label))
        {
            throw new InvalidOperationException("非法操作:未指定标签!");
        }
        LightMapInfo info = Lightmaps.Find(v => v.name == label);
        if (null != info && !forceOverride)
        {
            throw new InvalidOperationException($"非法操作:已存在的条目 {label},强制覆盖请勾选 ForceOverride");
        }
        if (null == info)
        {
            info = new LightMapInfo { name = label, Lightmaps = new LightMapPairArray() };
            Lightmaps.Add(info);
        }
        else
        {
            info.Lightmaps.Clear();
        }
        foreach (var item in LightmapSettings.lightmaps)
        {
            info.Lightmaps.Add(new LightMapPair { lightmapColor = item.lightmapColor, shadowMask = item.shadowMask });
        }
        Debug.Log($"数据装载完毕,共计:{LightmapSettings.lightmaps.Length}个");
    }

    public void Apply(string name) 
    {
        LightMapInfo info = Lightmaps.Find(v => v.name == name);
        if (null == info )
        {
            throw new InvalidOperationException($"非法操作:不存在此数据 {name}!");
        }
        LightmapSettings.lightmaps = info.Lightmaps
            .Select(v=>new LightmapData { lightmapColor=v.lightmapColor, shadowMask = v.shadowMask })
            .ToArray();
    }
    public void Apply(int index) 
    {
        if (index<0||index>=Lightmaps.Count)
        {
            throw new InvalidOperationException($"非法操作:指定的索引超 {index} 下标!");
        }
        LightmapSettings.lightmaps = Lightmaps[index].Lightmaps
            .Select(v => new LightmapData { lightmapColor = v.lightmapColor, shadowMask = v.shadowMask })
            .ToArray();
    }

    [System.Serializable]
    public class LightMapInfoArray : ReorderableArray<LightMapInfo> { }
    [System.Serializable]
    public class LightMapPairArray : ReorderableArray<LightMapPair> { }

    [System.Serializable]
    public class LightMapInfo
    {
        public string name;
        [Reorderable, SerializeField]
        public LightMapPairArray Lightmaps;
        public void Clean()
        {
            Lightmaps.Clear();
            name = string.Empty;
        }
    }

    [System.Serializable]
    public class LightMapPair
    {
        public Texture2D lightmapColor;
        public Texture2D shadowMask;
    }

}

tips: 使用了这个插件Unity-Reorderable-List

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容