Unity Lightmap&LightProbe局部动态加载(亲测2020以及以上版本官方修复了)


环境

Unity : 2020.3.37f1(后续发现 2020.3.37f1 官方是支持 多场景加载的烘焙信息的还原的,-_-!!!
Pipeline : BRP

如果你使用的是 2020, 或是以上的 unity 版本,请无视此问题


原因

因为 unity 内置的 bake 系统是只能跟单个场景走的

并且,运行时,只有 active == true 的场景对象才有小

如果你当前 additve 的方式 load 了多个场景,并且每个场景都有自己的 lightmap

那么其他不是 active == true 的场景对象的 lightmap 将会失效

(我是没搞懂为何 unity 这功能不制作好一些,这个功能应该很多项目都会有使用到的)


恰好,我们项目之前加载关卡的代码不是使用 加载 scene 的方式

而是使用 加载 prefab 的方式,一个prefab 就是一个 scene 里面的内容

那么这个关卡中如果烘焙了 lightmap的话,走 加载 prefab 的话, lightmap 都会失效

所以才搞这么一出笔记


解决方法

只要通过运行时,设置好 Renderer.lightmapIndex, Renderer.lightmapScaleOffset 即可


实践


场景A烘焙效果,并创建A Prefab

在这里插入图片描述


场景B烘焙效果,并创建B Prefab

在这里插入图片描述


单独Load A/B Prefab + 烘焙效果

可以看到 SingleAPrefabSceneSingleBPrefabScene 场景也有 Baked Lightmaps 的内容
在这里插入图片描述
在这里插入图片描述


同时将A,B场景的内容放到同一个场景内容,同时应用不同的 lightmap 烘焙

在这里插入图片描述


方案1 - 可以同一个场景中使用多个场景的烘焙结果

// jave.lin : 加载自定义的 lightmap 信息

using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class LoadCustomLightmapInfo : MonoBehaviour
{
    // jave.lin : 不要使用天空和,因为这个是在运行前决定的,
    // 中途设置除了 camera.clearFlag 有小,其他 renderer 的 reflection 都会有问题,
    // 建议使用 reflection probe 来替代
    //public Material skyboxMat;
    public List<LightmapDataAndRendererBinder> lightmapDatas;

    public bool refreshLightmap = false;
    public bool updated = true;

    private Dictionary<int, int> lightMapIndexDict;

    private void Start()
    {
        refreshLightmap = false;
    }

    private void Update()
    {
        if (!updated)
            return;

        if (!refreshLightmap)
        {
            refreshLightmap = true;

            LightmapData[] lightmaps = null;

            if (lightmapDatas != null)
            {
                // jave.lin : 刷新映射 idx
                if (lightMapIndexDict == null)
                {
                    lightMapIndexDict = new Dictionary<int, int>();
                }
                else
                {
                    lightMapIndexDict.Clear();
                }

                // jave.lin : 统计总共使用到多少个 texture 2d array的元素
                // 计算 light map texture 属于哪个索引,相同的索引都会放在同一个 lightmapData 中
                var lightmapDataCount = 0;
                for (int i = 0; i < lightmapDatas.Count; i++)
                {
                    var lightmapDataBinder = lightmapDatas[i];
                    if (lightmapDataBinder.lightmapData.lightmapColor == null)
                    {
                        continue;
                    }
                    var key = lightmapDataBinder.lightmapData.GetTex2DHashCode();
                    if (!lightMapIndexDict.TryGetValue(key, out int lightmapDataIDX))
                    {
                        lightmapDataIDX = lightmapDataCount++;
                        lightMapIndexDict[key] = lightmapDataIDX;
                    }
                    lightmapDataBinder.lightmapDataIndex = lightmapDataIDX;
                }
                // jave.lin : 根据统计出来的数量作为 lightmapData 的数组大小
                lightmaps = new LightmapData[lightmapDataCount];
                for (int i = 0; i < lightmapDatas.Count; i++)
                {
                    var lightmapDataBinder = lightmapDatas[i];
                    var lightmapIDX = lightmapDataBinder.lightmapDataIndex;
                    if (lightmapIDX < -1 || lightmapIDX > lightmapDataCount - 1)
                    {
                        continue;
                    }
                    var lightmapData = lightmaps[lightmapIDX];
                    if (lightmapData == null)
                    {
                        lightmapData = new LightmapData();
                        lightmapData.lightmapColor = lightmapDataBinder.lightmapData.lightmapColor;
                        lightmapData.lightmapDir = lightmapDataBinder.lightmapData.lightmapDir;
                        lightmapData.shadowMask = lightmapDataBinder.lightmapData.shadowMask;
                        lightmaps[lightmapIDX] = lightmapData;
                    }
                    // 设置 renderer 的 lightmap 属性
                    var meshRenderer = lightmapDataBinder.meshRenderer;
                    meshRenderer.lightmapIndex = lightmapIDX;
                    meshRenderer.lightmapScaleOffset = lightmapDataBinder.lightmapScaleOffset;
                    //meshRenderer.scaleInLightmap = lightmapDataBinder.scaleInLightmap;
                    //meshRenderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.BlendProbesAndSkybox;
                }
            }

            LightmapSettings.lightmaps = lightmaps;
            //RenderSettings.skybox = skyboxMat;
            //RenderSettings.defaultReflectionMode = UnityEngine.Rendering.DefaultReflectionMode.Skybox;
        }
    }
}


优化案例

将后续的 手动填写 LightmapDataAndRendererBinder 的方式,改用工具自动提取相关的数据即可


如果需要static batch的话,可以运行时调用 StaticBatchingUtility.Combine(parent) 来处理某个节点


方案2 - 在单个场景中使用

先在:RecordLightmapInfo2Prefab.unity 中烘焙
在这里插入图片描述
在这里插入图片描述

然后调用 RecordLightmapInfo.cs 脚本的 记录烘焙信息的功能,如下点击 RecordLightmapInfo 按钮即可
在这里插入图片描述

然后在另一个场景:Testing_Load_RecordLightmapInfo2Prefab.unity 中加载 RecordLightmapInfo2Prefab.prefab
并点击:CreateLightmapDatas 按钮即可
可以看到效果和在 RecordLightmapInfo2Prefab.unity 的效果是一致的
在这里插入图片描述


遗留问题

这种方法会导致:distance shadow mask 方式的阴影会失效

(貌似可以通过阅读 builtin shader 部分的 distance shadow mask 的 shader 源码处理)


Project


References


重要重要重要 - 2021.3.10, 2020.3.7 试了以下,官方完善了此功能

因为刚刚好和其他项目组的同事在聊到这款功能问题的时候
刚刚好他们使用的比较高版本的 unity : 2021.3
他说他们没有这个问题,都是 unity 在 load additive scene 的时候
每个场景自行烘焙的光照都会自动加载自己的烘焙信息

我刚刚开始不新,后来他截图,我半信了
在这里插入图片描述

然后通过自己试验,还真的完善了

真的是服了,不过不知道这个项目升级到 2021.3 会不会有兼容性问题

OK,下面试验结果出来了,而且是连 2020.3.7 都完善了

看来还是看去看看 unity 版本对应的 release notes 的, -_-!!!
在这里插入图片描述

而且我试过了,Distance Shadow Mask 也可以有效的,如下:

场景B中我 shadow distance 设置为 10(比较小,方便查看 distance shadow mask 效果)

我先故意错开 Cube 的位置,便于查看 distance shadow mask 效果
请添加图片描述

然后我在 main 场景 同时加载 A,B
在这里插入图片描述

A没有 distance shadow mask,light 纯 bake 阴影
B有 distance shadow mask,light 是 mixed 的

所以下面演示可以看到 A 没有 distance ,B 有,如下:

同样故意错开 Cube 的位置,便于查看 distance shadow mask 效果
请添加图片描述

唯独:Scene Env map 信息冲突了,这个不是代码问题,而是设计上的取舍的问题了

如下:
A 场景使用的 Skybox 是:Panorama_Skybox
在这里插入图片描述

B 场景是:UnityDefault_Skybox
在这里插入图片描述

Main 场景加载两个 A,B 场景,然后 Skybox 就是选择问题了
在这里插入图片描述


版权声明:本文为linjf520原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。