RhinoFreak 发表于 2022-6-18 09:39

【小玩意儿】UE中的浮点数精度问题

今天来随便聊聊UE4的浮点数精度问题,可能对做移动端开发的朋友有点帮助吧(主要之前在UDN看到有人问过这个),这篇文章的目的也就是给大伙们提个醒,有这么个事儿,万一以后遇到了少踩点坑。不过如果你不打算做移动端的Shader开发,下面的内容应该也没有什么用,不如拿这时间打一把炉石。

half和float这两种浮点数类型,对于各位写Unity Shader的朋友或者各种大佬来说,应该都是非常熟悉的了。但在写UE4材质的时候,引擎并没有很好地为我们区分这两种类型,于是有的朋友可能就对此根本没有概念。本来这也不算什么问题,PC上的显示正常得很,能出什么问题呢?
问题就是UE4根本没管你移动端啊!


为了性能,UE4默认让材质编辑器中连的大部分东西都用的half浮点数,电脑上的显卡写作half实际用的float,然而移动端的half那真的是half。

先来看看材质编辑器中连的节点生成的HLSL代码。随便连个材质。


可以看到这几个节点生成的HLSL代码,类型用的是MaterialFloat



在引擎目录\Engine\Shaders\Private里的Common.ush可以看到对MaterialFloat的定义,在像素着色器阶段,这个东西等于half



那么用half会有什么问题呢?
首先是half数字最大也就大概65504,超过这个大小的数字全都会被限制在65504。这个大小限制,两个三位数相乘可能就爆掉了。
然后是数值越大越不精准,这是浮点数天生的问题,但half由于数值范围太小,这个问题相比正常的单精度float,突出很多。数值在几十上百的时候,小数点后的数字就已经很不准了。



百度百科对半精度浮点数的定义

案例测试

下面我们来看几个例子。
地形案例

打开一个UE4,新建一个地形材质(或者随便什么材质),随便连一下,贴图平铺次数随便给一下,然后在UV计算后面加个100,让UV从100开始。


在电脑上显示很正常。


然后打包到手机上看看。如下图所示


拉近之后可以发现贴图已经成马赛克了,因为此时用来采样的UV坐标,由于精度不足,已经在屏幕上已经不够连续了。

水材质案例

再新建一个简单的水材质,水长得好不好看不重要,重要是里面用Panner这个函数来做个简易的流动效果。
Panner(或者说Time)引起的精度问题可以说是最常见的问题了,特别是很多跟着网上教程做的特效小哥,把东西打包到手机之后发现,怎么过了几分钟特效变成马赛克了。



电脑显示正常。



然后打包到手机。刚进去的时候可以看到水面是正常的,然后过1分钟,可以看到水面变成了马赛克。如果是再放久一点,马赛克会更加严重,并且会变得卡顿。


这是由于panner直接使用了时间去做UV偏移,时间不断增长的情况下,采样用的UV坐标数值很快就到了half精度不够的范围,然后导致马赛克出现。

天空案例

再做个例子,写个简单的材质,用来当作天空盒。重点是材质里面用使用位置减去摄像机位置,然后做normalize,用来采样一张Cubemap。



电脑上看着很正常。



手机上他长这样。



由于像素的世界空间位置减去摄像机位置,可能得到的数值是好几百上千。用RenderDoc随便看了一个点,发现这个数值已经上万了。。。(为了做例子,用力有点过猛)



然后normalize的第一步就是自己dot自己,这一步操作轻轻松松突破了half的最大数值65504。那么后面剩余的计算那都是不准的了。
在RenderDoc中让手机这一步计算改用float单精度浮点数,发现画面正常了。说明这就是由half引起的问题。



快速定位问题

快速定位是否是精度引起的问题,可以用renderdoc快速测试一下。找到对应的DC,在Pipeline State窗口选中Fragment Shader点Edit编辑。


将mediump改成highp,点击Refresh,如果问题修复则说明是精度引起的问题。



解决方法

在编辑器中解决问题有这么一些方法。
最省事的方法,直接材质里勾上Use FullPrecision,让所有的MaterialFloat都变成float,不过这会带来一点性能影响,毕竟用half就是为了提升性能。UE5里多了一个选项会让这个影响范围小一点。


一个比较难用但看起来不得不这么做的方法,首先得清楚是哪一步导致精度出现问题,那么将对应的节点用Custom Node节点写出来,手动控制float和half的使用。不过这个方法坑就坑在返回值类型和输入值类型也都是half的,要求使用者对相关代码很熟悉。或者干脆整坨代码用Custom写。



可能是这么写的?

还有一些比较特殊的方法,比如UV相关的,取小数点再用;时间相关的,改用循环的时间;数值太大爆掉的,变小一点再送去做计算等等。

当然最好用的还是改引擎,可以手动针对某个节点设置其浮点数类型。修改难度不大,不知道为什么官方没有提供这个功能。

量子计算9 发表于 2022-6-18 09:40

官方做了,在ue5里,把用户节点用half,但不能指定某个节点的精度。也可以用custom node手写float解决

LiteralliJeff 发表于 2022-6-18 09:48

啥意思,没看明白[发呆]

ChuanXin 发表于 2022-6-18 09:51

就是材质编辑器里节点的用float,引擎的shader用half。用custom node就是哪用float就写hlsl代替原来的节点

IT圈老男孩1 发表于 2022-6-18 09:52

UE4不就是这样,你说UE5做了是做了啥

super1 发表于 2022-6-18 10:01

我做过一个需求就是在ue4里让单个材质函数支持float,改改材质hlsl翻译器就可以。

Doris232 发表于 2022-6-18 10:08

UE4改全精度连引擎里shader都变全精度了

DungDaj 发表于 2022-6-18 10:15

不是,是只对单个material function改,只要翻译器在翻译material function时改改类型就可以了

rustum 发表于 2022-6-18 10:20

就是支持在material function里用use full precision,这个选项在材质蓝图里有,类似
页: [1]
查看完整版本: 【小玩意儿】UE中的浮点数精度问题