|
前言
在开发手机游戏时,为了兼顾画面表现力和游戏运行效率,很多时候我们会选择使用光照贴图来作为场景的照明方案。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(&#34;unity_Lightmap_HDR&#34;);
}
}
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(&#34;Apply Lightmap Settings&#34;)]
public void ApplyLightmapSettings()
{
if (_lightmapInfos == null || _lightmapInfos.Length == 0)
return;
LightmapSettings.lightmaps = _lightmapInfos.Select(p => _CreateLightmapData(p)).ToArray();
Shader.SetGlobalColor(&#34;unity_Lightmap_HDR&#34;, _lightmapHDR);
}
[ContextMenu(&#34;Apply Lightmap Settings For Standard Shader&#34;)]
public void ApplyLightmapSettingsForStandardShader()
{
ApplyLightmapSettings();
if (_rendererInfos == null || _rendererInfos.Length == 0)
return;
Shader standard = Shader.Find(&#34;Standard&#34;);
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引擎的小伙伴们,如果大家喜欢,请积极给我点赞哦,在大家的鼓励下我会更有动力和大家一起分享我的一些经验心得。如果文章中有错漏的地方,也希望大家能够给我指出,我会及时更新的。谢谢大家。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|