咨询电话:400-810-1418服务与监督电话:400-810-1418转接2
  • 相关推荐
  • Unreal制作布料角色交互设计教程
  • Unreal材质优化实用教程
  • Houdini18.5于Unreal实时渲染
  • Unreal中Decal贴花拉伸运用技巧
  • Unreal地形高级材质之根据斜率分配材质
  • UE4偏门实用技巧分享
  • UE4手游如何渲染与优化环境反射?

    发布时间:2020-11-02 14:46:05

    今天跟大家分享下UE4手游如何渲染与优化环境反射?!本文介绍了四个小主题,分别是UE4 Mobile端的Skylight和ReflectionCapture之间的关系,如何让ReflectionCapture采集天光,ReflectionCapture的亮度校正算法分析及在期在移动端可能的优化。值得一提的是ReflectionCapture IBL的压缩是UE4.26中已被实现。

    这篇文章是UE4 反射球系列文章的完结篇,内容包含以下几个部分:

    1. SkyLight和Reflection Capture的Cubemap有何不同?

    2. Reflection Capture为何采集不到天空球Mesh?

    3. Reflection Capture的亮度是如何确定的?

    4. 对移动端来说,Skylight/Reflection Capture有哪些可用的优化?

    一、SkyLight和Reflectioin Capture

    SkyLight所提供的是直接光照,同时包含漫反射和高光反射,而Relfection Capture提供的是环境反射,只有间接高光且不包含漫反射。严格来说,这两部分光照信息的频度定位不同,是叠加关系,不能放一起比较。

    凡事都有意外,UE4在移动端的标准光照模型中SkyLight Cubemap和Reflection Capture Cubemap是互斥的——要么存在的是Skylight,要么存在的是Reflection Capture,且在选择时Relfection Capture的优先级高于Skylight。不光如此,移动端的Relfection Capture的范围也无效。因为两者的互斥关系移动端PBR渲染过程只需要采样一次cubemap,相比多次采样cubemap更廉价,能耗更低。

    但同时这些做法也带来了一些问题:

    1. 场景中同时存在Reflection Capture&Skylight时,因为Reflection Capture优先级高且范围无效,永远只可能Reflection Capture有效。

    2. 相对于非移动端来说,移动端的场景中IBL所提供的光照往往会来得更暗一些。

    3. 因为Reflection Capture的范围无效,物体在渲染时只取离它最近的那一个,也导致在场景制作过程中,需要区分室内室外,楼上楼下,多变的环境氛围时工作流几乎不可能实现,因为无法精确控制范围。这个问题对于想要制作高品质游戏场景来说,说致命并不为过。

    二、Reflection Capture 为何采集不到天空球

    在场景和TA的强烈要求下,我们在移动端修复了Reflection Capture的作用范围,想要在移动端让范围起作用,只需要在FScene::FindClosestReflectionCapture里查找和物体包围盒相交的的Reflection Capture并处理好Mobilebasepass的ShaderBinding参数设定即可。

    这个问题修复之后,TA很快又发现两个新的问题:

    1. 同样的一样IBL图,当它作为Skylight输入存在时比作为Reflection Capture输入时对亮度的贡献大很多。

    2. 放在室外的Reflection Capture,上半部分是黑的,一查原来是采集不到Stationary的天空球。

    第一个问题留到第三部分去说,第二个Reflection Capture采集不到天空球的问题,则是因为Skylight有一个选项用来控制天空球的Threshold。

    1.jpg

    这个选项的直接意思是:距离原点多远之后的场景物体属于天空球,这个默认值是1500米,即1500米以外的所有物体,都属于天空。因为UE4在非移动端的实现中Skylight和Reflection Capture相互叠加,且当Reflection Capture和SkyLight同时可见时,优先选用的是Skylight的部分。这样的话,在存在Skylight的室外,采集ReflectionCapture时确实不需要采集和存储天空球。

    这个设计初衷所带来的问题在于:你要是移动端的话,你就完蛋了——你室外的光滑物体和纯金属,上半球一片黑。黑夜给了你的黑色眼睛,是你看到了自己心理的阴影?

    我们来看看UE4在采集Reflection Capture时,是如何丢掉天空球信息的。

    1. 实现代码在ReflectionEnvironmentShader.usf 的CopySceneColorToCubeFaceColorPS函数中,代码如下所示:

    2.webp.jpg

    这段代码的解释为:当当前采样到的位置距离< 0.8 * threshold时,IBL的Alpha值为1,否则小于1,按1-Smoothstep曲线(似乎看到的实现,大多数SmoothStep都是三次曲线)方式趋向于0,当距离大于等于threshold时,Alpha必然为0,Alpha 值会被写入Cubemap的Alpha通道。

    2. SkyLightParametersValue参数传递的入口在ReflectionEnvironmentCapture.cpp中的FCopySceneColorToCubeFacePS类的SetParameters函数中,代码如下:

    3.jpg

    可以看到这儿的SkyLightParametersValue.x即为 Skylight上的SkyDistance Threshold值。

    3. 在FilterReflectionEnvironment函数中一开始执行一次premultiply alpha,这时alpha值同会乘以rgb值,所以会造成最终的cubemap中alpha为0的值也变成了全黑色,代码如下:

    4.jpg

    即:Color = 0 * srcColor + destAlpha * destColor

    经此一步之后,不管是移动端还是非移动端,其生成的Cubemap中被判为天空的部分已经全部为0,所以在反射球为移动端存储编码为RGBM时,早已没有了这部分颜色信息……

    三、移动端的IBL亮度计算

    上文说到使用同一张Cubemap作为Skylight和Reflection Capture输入时,得到的结果亮度不一致,于是乎TA提了一个BUG单。

    没过多久,场景美术发现无论是接受CSM实时阴影或是烘焙的ShadowMask,金属物体上的阴影总是比非金属上的阴影来得更黑一些,于是乎场景美术不光提了一个BUG单,还抱怨说UE4怎么这么多乱七八糟的问题,比隔壁另一个U字头的引擎还不如?

    Skylight和Reflection Capture亮度不一样的问题,是因为Skylight的cubemap在渲染时直接使用的是cubemap的原始亮度,而Reflection Capture在渲染时亮度经过了缩放(大部分时候,这个缩放值都是小数,也就是说:它都会变暗)。具体的实现在MobileBasePassPixelShader.usf的GetImageBasedReflectionLighting函数中。

    5.webp.jpg

    可以看到在Reflection Capture的情况下,SpecularIBL会乘以缩放值。这个值是通过MobileComputeMixingWeight函数计算出来的——如果你用的UE4版本在4.23之前,那么这个函数是不带Mobile的ComputeMixingWeight,Mobile版本只是简单的把ComputeMixingWeight的所有数据类型,由float改为了half*,算法完全一致。

    接下来我们详细拆一拆MobileComputeMixingWeight的算法,先看看这函数的全貌(因为源代码中的注释有很强的误导性,所以注释去被我去掉了,同时我也简化了一下代码的布局)。

    6.webp.jpg

    算法分为这几步:

    1. 计算一个0~1之间的MIxingAlpha值。

    7.jpg

    这个值由ReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight的x、y及当前像素的Roughness值来确定。Ref*Weight参数的计算过程。

    8.webp.jpg

    写成数学公式:

    x = 1.0/(b-a) ,b为结束Roughness值,a为开始Roughness值

    y = - a / (b-a)

    把上述x,y代入到MobileComputeMixingWeight中的MixingAlpha式中得

    MixingAlpha

    = smoothstep(0 ,1 , Roughness/(b-a) - a/(b-a)

    = smoothstep(0 , 1 , (Roughness - a)/(b-a))

    可以看到MixingAlpha值和Roughness值成正比,Roughness越大,则MixingAlpha值也越大,最大值不超过1(staturate所限)。

    2. 计算MixingWeight(Normalized Cubemap)

    9.jpg

    这一个变量的命名和原始的注释非常迷惑,按主流的说法,它该叫Normalized Cubemap,其作用是把当前IBL的间接漫反射亮度缩放到和从Lightmap或SH(ILC)中接收到的间接GI亮度一致。indirect_irradiance来源是物体当前像素的光照图的亮度(静态物体)或ILC的亮度(动态物体)。

    算法简化成公式如下:

    normalized_scale = indirect_irradiance / ibl_average_brightness

    一般的最终specular_IBL计算公式(UE4不完全一样,见步骤3说明):

    specular_IBL = sampled_cube * normalized_scale

    其中ibl_average_brightness来源是当前经过卷积后的cubemap所计算出来的平均亮度,即roughness为1时所采的这张1*1的最小mipmap的亮度。

    UE4中计算该IBL亮度时不是使用普通的亮度计算公式,而是使rgb的贡献平均化。

    ibl_average_brightness = dot(color.rgb ,float3(0.333,0.333,0.333)

    UE4的亮度计算之所以使用均值,是因为如果使用标准的亮度计算公式,无法处理一些特殊的IBL边界情形:当IBL中只存在蓝色时,亮度会非常小,而当IBL中只存在绿色时,亮度又会非常大。但这么做却也并不是最优的选择,我们将在第四部分进行说明。

    3. 插值得到最终的缩放系数

    10.jpg

    注意到步骤1中结论:Roughness值越大,MixingAlpha越大,当Roughness大于等于结束Roughness时,缩放系数完全等于步骤2中计算出来的normalized_scale;当roughness小于等于开始Roughnes时,缩放系数等于1; 缩放系数其它情形下处于[1,normalized_scale]之间。

    11.webp.jpg

    由于在大部分情形下,normalized_scale小于1,所以也就可以看到一个现象:越光滑的物体,反射球对它的影响就越强,越粗糙的物体,反射球对它的亮度影响就越弱。

    至于场景美术所提第二个问题——“金属物体上的阴影总是比非金属上的阴影来得更黑”,经查是TA做了一个很不PBR的材质规范:非金属材质没有AO通道,AO图直接乘到了BaseColor上;金属材质留有材质AO通道输入了AO图,BaseColor上不带光照和遮挡信息。由于材质AO会同时作用于BaseColor和间接光照的亮度(indirect_irradiance *= AO),所以金属物体的IBL缩放值会更小,从而更黑。

    四、UE4移动端IBL的可用优化

    1. 压缩格式 :UE4移动端的Skylight cubemap是用的float原生的图,既然定位等同于Reflection Capture,可以考虑使用RGBM的方式同样的压一压?Reflection Capture的RGBM在Cook时也不接受ASTC/ETC/PVR方式的压缩,要不要统一压成硬件支持的格式?(UE4.26 Preview中,移动端已实现Reflection Capture的Cubemap压缩,压缩格式为ETC2)

    2. Cubemap转2D纹理:到ES3.1都不支持CubemapArray,故IBL的变化会导致动态Instance失效,是否可以一下把 Cubemap转为经纬图或椭球纹理,从而使用TextureArray+Custom PrimitiveData进行动态Instance合批的最大化,从而有效的降低Drawcall ?转为2D纹理有2个小风险,经纬图的纹理浪费比cubemap大概有30%左右,有同事在小米6/小米9实测,相对于Cubemap,经纬图纹理采样的CacheMiss也更高。

    3.算法优化/效果优化:UE4的IBL Normalized算法使用的是标量的ibl_average_brightness和标量的indirect_irradiance来计算,这种方式计算所带来的问题:

    在未开启Lightmap方向性的情况下,lightmap的亮度只会计算上半图的亮度,而ILC中的亮度可能来自于任意方向,这可能会给使用ILC和Lightmap的物体带来不同的IBL亮度。

    12.webp.jpg

    由于ibl_average_brightness和indirect_irradiance两部分的亮度计算公式不一致,normalized的效果可能会因间接GI的颜色而使IBL反射出来的亮度不一致。

    由于ibl_average_brightness来源于cubemap全球面的总和,而indirect_irradiance只可能来源于当前像素法向上半球的输入,故其亮度normalized的结果并不会使两者的亮度相等,而可能会导致IBL未能表现出应有的亮度,同时也会降低IBL的方向性。

    MobileComputeMixingWeight的MixingWeight计算代码实现上未防止越界~~

    COD黑色行动和战神4中的IBL Normalize使用的是3阶SH。

    13.webp.jpg

    本文来源:知乎专栏“图形游戏和宅”

    文 | Jiff