js精度丢失解决办法_webgl中的精度问题

b79e868592efb2cfdcf21a8bb114cb76.png

BIM大场景以及GIS场景下一个比较容易出现的问题就是渲染抖动的现象。其本质是由于图形渲染精度与实际精度不匹配导致的。接下来,我们就详细聊聊webgl精度丢失导致的渲染问题,以及相关解决办法。

问题起因

JavaScript 中的Number类型精度为double类型,而一般GPU的图形接口api(如opengl、DirectX等)中浮点数精度为float,所以数据在cpu传入GPU做渲染过程中会有精度损失,从而导致了渲染中的闪动问题。

认识浮点数

float与double都是常见的计算机浮点数表示类型。区别在于flot为单精度浮点数,double是双精度浮点数。具体的区别在哪里呢,让我们细细的品一品。

float和double的浮点数都是使用二进制的科学计数法表示,在存储中都分为三个部分:

符号位(Sign) : 0代表正,1代表为负 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储 尾数部分(Mantissa):尾数部分

其中主要区别在于存储的长度,float用32个bit表示,double用64个bit表示。

float类型的具体位数描述:

| 符号位 | 指数位 | 尾数部分 |

| 1 | 8 | 23 |

float的指数位为8位,范围为 正负 2的7次方即-127~128,所以float的数据范围其实是-2^128 ~ +2^128。尾数部分为23位 2^23 = 8388608,一共7位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字。

double类型的具体位数描述:

| 符号位 | 指数位 | 尾数部分 |

| 1 | 11 | 52 |

double的指数位为11位,范围为 正负 2的10次方即-1023~1024,所以double的数据范围其实是-2^1024 ~ +2^1024。尾数部分为52位 2^52 = 4503599627370496,一共16位,即double的精度为15~16位有效数字。

webgl中的精度设置

在webgl的shader中,我们可以在第一行使用precision关键字进行精度设置。声明变量精度高低的三个关键子lowpmediumphighp。注意不同的shader里面有默认值,如果不指定或者指定错误,会导致编译报错。 顶点着色器默认精度

precision highp float;
precision highp int;
precision lowp sampler2D;
precision lowp samplerCube;

片元着色器默认精度

precision mediump int;
precision lowp sampler2D;
precision lowp samplerCube;

重现问题

那么,对于精度丢失会造成那些问题呢。我们来一起看看。比如你在里离原点很远的地方(类似距离x 5000000个单位)有个立方体,长宽高都为1。 然后放置相机到立方体正前方。当我们移动相机时,可以看到非常诡异的闪动,如下图:

5a37f57bc1c1f629e7d592a7cc9875aa.png

顶点数据:

var vertices = [
5000000.5, 0.5, 0.5, 5000000.5, 0.5, -0.5, 5000000.5, -0.5, 0.5, 
5000000.5, -0.5, -0.5, 4999999.5, 0.5, -0.5, 4999999.5, 0.5, 0.5, 
4999999.5, -0.5, -0.5, 4999999.5, -0.5, 0.5, 4999999.5, 0.5, -0.5, 
5000000.5, 0.5, -0.5, 4999999.5, 0.5, 0.5, 5000000.5, 0.5, 0.5, 
4999999.5, -0.5, 0.5, 5000000.5, -0.5, 0.5, 4999999.5, -0.5, -0.5, 
5000000.5, -0.5, -0.5, 4999999.5, 0.5, 0.5, 5000000.5, 0.5, 0.5,
4999999.5, -0.5, 0.5, 5000000.5, -0.5, 0.5, 5000000.5, 0.5, -0.5, 
4999999.5, 0.5, -0.5, 5000000.5, -0.5, -0.5, 4999999.5, -0.5, -0.5]

从数据上可以看出,立方体的顶点坐标x所需要的是8位数据,已经超过了float最大有效位数7位,所以组成这个立方体的顶点会有精度的丢失,从而导致组成的立方体会不规则。

同样的问题也会出现在任何顶点数据偏大的渲染上,比如地图上的标识,BIM场景中的模型展示等等。

解决办法

RTC方案

RTC(Relative To Center)的方案将模型的顶点信息,转化为相对于某个中心点的坐标,以上面的例子为例,我们可以将模型的顶点坐标统一到相对(5000000,0,0)这个点下面,顶点数据变成了下面:

var vertices = [
0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5
...
]

此时,顶点坐标的数据相对于之前已经变成了一个相对值,然后将模型的矩阵做个translate到相对点,即把模型放到相对中心的位置上(modelmatrix)。我们将模型相对观测点的矩阵(modelviewmatrix)传入shader,因为模型离相机的位置已经变成了一个相对不那么大的数据,这样可以模型相对相机较近的矩阵精度可以保证,而离相机较远的矩阵精度可能会丢失(但是一般较远的情况显示稍微有点偏差也问题不大)。

使用2个float传高精度的数据

function doubleToTwoFloats(value) {
    var high;
    var low;
    if (value >= 0) {
        tempHigh = Math.floor(value / 65536) * 65536;
        high = tempHigh;
        low = value - tempHigh;
    } else {
        tempHigh = Math.floor(-value / 65536) * 65536;
        high = -tempHigh;
        low = value + tempHigh;
    }
    return [high, low];
}

上面的函数将数值中超过 65536 的部分用一个 32bit 数表示,其余部分用另一个 32bit 数表示。 在shader中使用类似如下的函数进行计算

uniform vec3 uViewerHigh;
uniform vec3 uViewerLow;

void main(void)
{
    vec3 highDifference = vec3(gl_VertexHigh - uViewerHigh);
    vec3 lowDifference = vec3(gl_VertexLow.xyz - uViewerLow);
    gl_Position = gl_ModelViewProjectionMatrix *
         vec4(highDifference + lowDifference, 1.0);
}

这种方式会增加数据的传输量,除了新增的两个uniform变量代表视点的高位、低位数据,普通的vertex数据也被切为高低两部分,进行分别计算。所以这种方式保证精度的同时也会增加数据量,造成数据带宽瓶颈,具体要视情况而定。

以上就是对webgl中出现的精度问题的回顾与总结,如有错误之处,欢迎批评指正,大家互相讨论。

参考:

cesium关于精度的解决方案

百度地图关于精度问题的文章

WebGL着色器32位浮点数精度损失问题

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值