找回密码
 立即注册
查看: 512|回复: 1

深入了解Unity的光照贴图(一)

[复制链接]
发表于 2021-4-15 09:25 | 显示全部楼层 |阅读模式
前言
在开发手机游戏时,为了兼顾画面表现力和游戏运行效率,很多时候我们会选择使用光照贴图来作为场景的照明方案。Unity引擎为我们提供了功能强大的光照贴图功能,让美术人员可以在Unity编辑器里方便的烘焙光照贴图。
但是即便如此,在实际使用的过程中,还是会因为对Unity引擎的了解不足,导致遇到一些问题。常见的比如,希望在自己写的Shader里使用Unity烘焙的光照贴图,或者是希望用Prefab来保存游戏场景,而不使用Unity的Scene来保存场景,这时该怎么处理。又或者是在Windows上显示正常的光照贴图,切换到移动平台后出现亮度丢失的情况。
下面我会逐步介绍Unity光照贴图的相关知识,在过程中逐个解答以上的问题。
1. Unity的光照贴图和相关的数据
1.1 Unity的光照贴图
当Unity完成了光照贴图的烘焙时,按照不同的设置,最多会生成三种不同的光照贴图。其中以_light结尾的是光照贴图,以_dir结尾的是平行光的方向图,以_shadowmask结尾的是ShadowMask的阴影通道图。我们暂时先只关注_light结尾的光照图,另外两种暂时先不管。
1.2 Unity光照贴图的三种品质
在用Unity烘焙光照贴图时,可以选择高、中、低三种品质,这里我们来了解一下这个品质设置的具体作用。当选择High Quality时,生成的光照贴图格式是浮点型的HDR贴图,在Windows平台下是BC6H(Direct3D11支持的一种压缩格式)。当选择Normal Quality时,生成的光照贴图是RGBM编码的32位贴图,当选择Low Quality时,生成的是被称为DLDR的32位贴图。
因为在移动设备上不支持BC6H的压缩格式,需要用其他格式来压缩浮点型的HDR贴图,会导致光照贴图的容量变大。如果在Windows平台使用了BC6H格式的贴图,直接切换到ios或安卓平台后,贴图会直接被转换成pvrtc4或etc2的压缩格式,从而导致亮度的丢失。
为了避免这种情况,我们一般在烘焙光照贴图时,会选择Normal Quality这一档,对应生成的是RGBM编码格式的光照贴图。
1.3 RGBM编码格式的光照贴图
RGBM的编码方式其实并不复杂,我们可以看一下Unity内置管线里对应的Shader代码。
inline half3 DecodeLightmapRGBM (half4 data, half4 decodeInstructions)
{
    // If Linear mode is not supported we can skip exponent part
    #if defined(UNITY_COLORSPACE_GAMMA)
    # if defined(UNITY_FORCE_LINEAR_READ_FOR_RGBM)
        return (decodeInstructions.x * data.a) * sqrt(data.rgb);
    # else
        return (decodeInstructions.x * data.a) * data.rgb;
    # endif
    #else
        return (decodeInstructions.x * pow(data.a, decodeInstructions.y)) * data.rgb;
    #endif
}这个函数里,我们可以把注意力放在中间的这行上,
return (decodeInstructions.x * data.a) * data.rgb;这是Unity引擎在Gamma space下对RGBM格式光照贴图的解码方式,其中decodeInstructions对应的是unity_Lightmap_HDR这个4维向量。在百度查阅RGBM编解码后,我们可以知道这里的x分量表示的是Unity生成的HDR光照贴图里的最大光照范围。
1.4 其他相关的数据
首先来看一下Lighting面板,在这里我们能看到刚才烘焙后生成的所有光照贴图。查阅Unity文档,我们可以知道这些贴图是在UnityEngine.LightmapSettings.lightmaps里记录的。
再来看一下几个用到光照贴图的模型的Mesh Renderer,在这里我们能看到这个模型用到了哪一张光照贴图,以及对应的UV scale和offset。
这些就是除了光照贴图之外,Unity里记录的和光照贴图相关的数据了,后面我们会需要用到这些数据。
2. 用Prefab替代Scene来保存场景
某些情况下,我们会希望用Prefab来保存各种游戏场景,通过切换不同的Prefab来实现游戏内场景的切换。同时,也希望能使用Unity烘焙的光照贴图,在切换不同的Prefab时,能显示对应的光照贴图效果。
为此,我们需要在各个代表场景的Prefab里记录对应的光照贴图信息,最直接的方法就是通过绑定在Prefab上的Mono脚本来记录相关信息。
2.1 通过Mono脚本来记录光照贴图的信息
在通过上一节的分析后,我们知道需要记录以下信息:
    生成的所有光照贴图的Texture对象对于景中所有使用光照贴图的模型,需要保存该模型用到的光照贴图索引,以及光照贴图UV的scale和offsetunity_Lightmap_HDR这个4维向量
在明确了需要保存的信息后,我们可以很容易的通过Mono脚本来获取并记录这些信息。首先,定义用来保存光照贴图信息的数据结构和对应的成员变量。
[System.Serializable]
struct RendererInfo
{
    public Renderer Renderer;
    public int LightmapIndex;
    public Vector4 LightmapScaleOffset;
}

[System.Serializable]
struct LightmapInfo
{
    public Texture2D Color;
    public Texture2D Dir;
    public Texture2D ShadowMask;
}

[SerializeField]
Vector4 _lightmapHDR;

[SerializeField]
LightmapInfo[] _lightmapInfos;

[SerializeField]
RendererInfo[] _rendererInfos;
然后,通过访问LightmapSettings来获取Unity生成的光照贴图,再通过遍历所有的Mesh Renderer,来记录光照贴图的索引和UV的scale和offset。
[ContextMenu("Save Lightmap Settings")]
public void SaveLightmapSettings()
{
    _lightmapInfos = null;
    _rendererInfos = null;

    if (LightmapSettings.lightmaps != null)
    {
        _lightmapInfos = LightmapSettings.lightmaps.Select(p => _CreateLightmapInfo(p)).ToArray();

        var renderers = GetComponentsInChildren<Renderer>();

        if (renderers != null)
        {
            _rendererInfos = renderers.Select(p => _CreateRendererInfo(p)).ToArray();
        }

        _lightmapHDR = Shader.GetGlobalColor("unity_Lightmap_HDR");
    }
}

LightmapInfo _CreateLightmapInfo(LightmapData lightmapData)
{
    var info = new LightmapInfo();

    info.Color = lightmapData.lightmapColor;
    info.Dir = lightmapData.lightmapDir;
    info.ShadowMask = lightmapData.shadowMask;

    return info;
}

RendererInfo _CreateRendererInfo(Renderer renderer)
{
    var info = new RendererInfo();

    info.Renderer = renderer;
    info.LightmapIndex = renderer.lightmapIndex;
    info.lightmapScaleOffset = renderer.lightmapScaleOffset;

    return info;
}
2.2 通过Mono脚本来应用记录在Prefab里的光照信息
这里假设我们是使用Unity的Standard Shader,当我们加载了一个Prefab,并希望能显示记录的光照贴图效果,我们需要将记录在Mono脚本里的这些信息重新写回到Unity的LightmapSettings和Mesh Renderer里。
LightmapData _CreateLightmapData(LightmapInfo lightmapInfo)
{
    var data = new LightmapData();

    data.lightmapColor = lightmapInfo.Color;
    data.lightmapDir = lightmapInfo.Dir;
    data.shadowMask = lightmapInfo.ShadowMask;

    return data;
}

[ContextMenu("Apply Lightmap Settings")]
public void ApplyLightmapSettings()
{
    if (_lightmapInfos == null || _lightmapInfos.Length == 0)
        return;

    LightmapSettings.lightmaps = _lightmapInfos.Select(p => _CreateLightmapData(p)).ToArray();

    Shader.SetGlobalColor("unity_Lightmap_HDR", _lightmapHDR);
}

[ContextMenu("Apply Lightmap Settings For Standard Shader")]
public void ApplyLightmapSettingsForStandardShader()
{
    ApplyLightmapSettings();

    if (_rendererInfos == null || _rendererInfos.Length == 0)
        return;

    Shader standard = Shader.Find("Standard");
   
    foreach (var info in _rendererInfos)
    {
        var renderer = info.Renderer;

        renderer.lightmapIndex = info.LightmapIndex;
        renderer.lightmapScaleOffset = info.lightmapScaleOffset;

        var count = renderer.sharedMaterials.Length;

        for (var i = 0; i < count; i++)
        {
            renderer.sharedMaterials.shader = standard;
        }
    }
}
通过运行脚本函数ApplyLightmapSettingsForStandardShader,我们可以将光照贴图的信息重新写回到LightmapSettings和Mesh Renderer里。
3. 在自己写的Shader里使用Unity的光照贴图
出于种种原因,在很多时候我们并不想直接使用Unity内置的Standard Shader,但同时我们又希望使用Unity的光照贴图。这种情况下,我们可以通过预处理的方法来修改场景模型的材质,使我们的Custom Shader能正常的显示Unity的光照贴图效果。
3.1 给Custom Shader设置正确的Lightmap
在弄清楚了怎么在Prefab里记录Unity生成的Lightmap以后,这个问题就变得非常简单了。
首先,我们可以遍历Prefab里记录的所有Renderer信息,获取到每个Renderer对应的LightmapIndex。用这个来索引记录的Lightmap信息数组,获取到对应的Lightmap对象,并将这个Texture对象直接传递给Custom Shader。
3.2 给Custom Shader设置Lightmap的scale和offset
在获取到Lightmap对象的同时,我们也可以获取到UV的scale和offset信息,同样一起传递给Custom Shader。
3.3 用正确的方式来采样Lightmap
除了Lightmap和UV,我们还需要将光照贴图的最大亮度也传给Shader,这样才能在Shader里正确解码RGBM格式的光照贴图。
这里我们只关注Gamma空间的采样方式,Linear空间的采样方式可以参考Unity内置的Shader代码。以下是PS里的公式:
BakedColor = LightmapColor.rgb * LightmapColor.a * _lightmap_HDR.x;
FinalColor = BakedColor * MainTexColor
这里的LightmapColor是采样光照贴图获取到的颜色,_lightmap_HDR是我们记录在Prefab里的4维向量,在Gamma空间下,我们只用到了x分量,也就是最大亮度范围。
因为相对简单,所以我就不给再给出详细的Shader代码了,相信大家都可以自己搞定。
4. 小结
到此,我们文章开头中提到的三个问题基本已经都有了答案,我们再做个简单的小结。我们先介绍了Unity光照贴图的三种不同格式,以及LightmapSettings和Mesh Renderer里记录的相关信息,再介绍了怎么用Mono脚本在Prefab里记录这些信息。最后我们介绍了怎么在自己编写的Shader里使用这些信息,以及怎么处理RGBM格式的光照贴图。这些便是本文的全部内容了。
希望本文能帮助到所有正在学习或使用Unity引擎的小伙伴们,如果大家喜欢,请积极给我点赞哦,在大家的鼓励下我会更有动力和大家一起分享我的一些经验心得。如果文章中有错漏的地方,也希望大家能够给我指出,我会及时更新的。谢谢大家。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-4-15 09:28 | 显示全部楼层
https://answer.uwa4d.com/question/5b9f7ed8f31d7a33f3469a2d 老哥看看这个
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-20 22:30 , Processed in 0.092975 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表