欢迎参与讨论,转载请注明出处。
前言
Demo目前的实时光影虽已完成,但考虑到不同的配置设备,还是得做出不同档次的光影方案。那么烘焙光照(Lightmapping)与光照探针(Light Probes)就免不了了。本文将结合项目实际需求,讲述遇到的问题及解决方案。项目引擎版本为2019.4,渲染管线为URP。
初步的烘焙
首先要做的自然是对Shader增加光照烘焙与探针的支持,照抄URP的SimpleLit Shader即可。大致要点如下:
- Shader添加multi_compile:
LIGHTMAP_ON
与_MIXED_LIGHTING_SUBTRACTIVE
,这表示Shader会参与光照贴图与混合光照 - 顶点着色器参数添加
half2 lightmapUV : TEXCOORD1;
,这是光照贴图的UV - 片元着色器参数添加
DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 1);
,这是URP自带的宏,根据LIGHTMAP_ON决定配置光照烘焙或探针的参数(lightmapUV or vertexSH),最后的参数1
决定是第几个TEXCOORD - 在顶点着色器调用
OUTPUT_LIGHTMAP_UV(lightmapUV, lightmapScaleOffset, OUT)
与OUTPUT_SH(normalWS, OUT)
宏,它们将根据情况配置lightmapUV与vertexSH - 在片元着色器对光照贴图或探针进行取色(
SAMPLE_GI(lmName, shName, normalWSName)
),最后将之加入到着色环节即可 - 若是想要烘焙模式下也能接受实时阴影,记得调用
MixRealtimeAndBakedGI
函数
总的来说都封装好了,照着拼凑而已。那么事不宜迟,直接按照默认的烘焙配置整个看看,记得要将GameObject的Static里的Contribute GI
勾选方可参与烘焙:
看着似乎还不错,那么对比下实时看看吧:
这么一看还是有不少差距的,必须要让烘焙与实时的效果高度接近才行呐——
ShadowMask
经过与烘焙设置一番斗智斗勇后,我发现我要的仅仅是让阴影烘焙,以节省阴影的运算罢了。什么全局光照、烘焙自带的着色等等都是不需要的。为此我尝试过不少骚操作:生成光照贴图后进行二值化处理、直接在Shader对烘焙色进行处理等……可惜这些方案都只是治标不治本,要么在流程上繁琐,要么性能不佳,要么无法应对所有情况。最终我把目光放在了烘焙三模式之一的ShadowMask,它将单独生成阴影贴图,那么若是我只用它,抛弃光照贴图,便可达到目的了。
不幸的是,URP并没有支持ShadowMask,官网显示仍处于In research状态。幸好网上有其他人做了实现ShadowMask的教程,顺便也学习了一波可编程渲染管线(SRP)的基础知识。经过研究发现,ShadowMask的添加并不复杂,甚至可以说是URP主动将之关闭了(严重怀疑是故意拖到后面做,显得有活干)。当然这么干了之后就表示需要维护自己的URP版本了,顺便将之开源了。
SRP本质上是开放了一个可供用户定制的表层,多数核心功能还是封装好的。ShadowMask也不例外,其生成附属于烘焙模块。我们要做的只是添加一些设置,以及相应的Shader支持罢了:
|
|
Shader方面要做的调整也不多,URP本身自带ShadowMask的贴图变量TEXTURE2D(unity_ShadowMask);
,其UV与光照贴图一致,复用即可。记得在Shader添加multi_compile SHADOWS_SHADOWMASK
以判别是否处于ShadowMask模式下。
|
|
大致要做的事情就这么多,烘焙设置除阴影方面外,能怎么快就怎么设置(反正也用不上光照贴图了),一般来说需要注意的有Bounces
要设为1,不然阴影会不完整。Flitering
将对阴影贴图做边缘柔和处理,Lightmap Resolution
与Lightmap Size
决定阴影质量,参考如下:
另外需要注意的是,阴影精度很大程度上取决于模型的大小,因为一个模型只能有一张光照/阴影贴图,在贴图大小定死上限的前提下,模型越大贴图的解析度自然越低。那么来看看效果吧,图一为实时,图二为烘焙:
效果可以说是高度接近了,干掉了光照贴图后着色变得完全一致,阴影贴图在合理的设置下也达到了高度接近实时的效果。坡肥!
光照探针
现在虽然实现了高度接近实时的阴影烘焙,但显而易见,当人物走向阴影处便会是这样的结果:
在某些游戏也许不太理会这种现象,但这也太捞了,光照探针便是为了解决这个问题而生的。通过在场景布置探针,将会根据动态对象附近的探针取色决定明暗度:
光照探针如果要手动布置那实在是太麻烦了,于是我使用了这个插件,通过简单的设置暴力的去平铺一波:
根据官方文档说法,探针数量与性能成反比(但越多越精确)。但此插件平铺并不会把探针置于模型内部,以及对比了下《使命召唤手游》的光照探针,感觉还行:
Shader方面没什么要改的,在URP获取光照函数GetMainLight()
本身自带了对光照探针的着色处理(附加在light.distanceAttenuation
中),由于不需要用到全局光照,之前的OUTPUT_SH
之类的都可以删了。当然有个现象需要注意下:
可以看到在暗处时实在是太黑了(也许是放弃了全局光照导致),于是我们加个约束,将暗值约束在光照Strength到1:
|
|
很好,这下可以说是大功告成了!
后记
最后演示下不同光影品质下的差别吧,分别为低、中、高:
话虽如此,可我发现目前直接把高品质光影扔到iPhone8下居然稳定59帧,太强了……