问题

WebGL浮点数精度最大的问题是就是因为js是64位精度的,js往着色器里面穿的时候只能是32位浮点数,有效数是8位,精度丢失比较严重。

这篇文章里讲了一些处理方式,但是视坐标这种方式放在我们的场景里不适用
 
 

分析

在基础底图中,所有的要素拿到的都是瓦片里面的相对坐标,坐标范围在0-256之间。在每次渲染时都会重新实时计算瓦片相对中心点的一个偏移来计算瓦片自己的矩阵,这种情况下精度损失比较小,而且每个zoom级别都会加载新的瓦片,不会出现精度损失过大问题。
 
但是对于一些覆盖物,比如marker、polyline、label使用的都是经纬度,经纬度小数点后位数比较多,从js的数字传入到gl中使用的gl.FLOAT是32位浮点数,小数点只能保证到后4位或者5位。在18级会出现严重的抖动问题。
 
文章中提到了几种解决方案,像mapbox使用的是第二种方案,将覆盖物比如marker、polyline、polygon都按照瓦片切分,经纬都转换成瓦片网格里面的0-256数字。这种方法每次zoom变换都要按照新的网格来重新切分。尤其到了18级往后,比如室内图22级,网格非常小,导致切分时间特别长。

继续尝试发现mapbox中也有类似问题:https://github.com/mapbox/mapbox-gl-js/issues/7268

mapbox这里也是使用了转换到视空间。但这种方式并不适合我们。
 
继续思考,实际这个问题原因是32位浮点数有效位不够,我们要找一个相对坐标为基准,其他的覆盖物坐标都是以这个点为基准,这个相对原点的坐标保留大部分数字,剩下的相对坐标数字尽量小,这样有效位尽量留给更多的小数位。然后把这个相对坐标分为两部分Math.fround(lat),lat - Math.fround(lat);然后两部分分别在着色器重进行计算结果在相加。
6.17号第一次按照这个逻辑执行了,搞到凌晨四点多,发现并不能解决浮点数精度问题。18号跟安哥讨论了下,首先这个高位和低位不能直接在着色器里相加后进行计算。尽管设置了highp类型的float还是不行,这里面可能是因为后面有做了一些大数的乘法计算导致精度被消磨掉了。而后有做了高位的低位分别计算最后在相加,结果也不行,猜测是因为里面做了瓦片坐标转换,有一部分256 x 2^n这种计算,导致精度损失。也有可能是在某些机型上即使设置了highp实际使用的浮点数也是32位的,按照这篇文章说法(https://blog.csdn.net/abcdu1/article/details/75095781)来看,下面这个确实是得到32位浮点数https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices
map.renderEngin.gl.getShaderPrecisionFormat( map.renderEngin.gl.VERTEX_SHADER, map.renderEngin.gl.HIGH_FLOAT )

解决

最终从deck.gl中找到了一种解决方案,也是将传入的数据拆分成一个高位和低位。

project_uCoordinateOrigin使用的是地图中心点的经纬度坐标

其中着色器中的一部分关键是project_uCommonUnitsPerWorldUnit和project_uCommonUnitsPerWorldUnit2这两个uniform量。跟踪代码后发现在这里有计算:
getDistanceScales() {
// {latitude, longitude, zoom, scale, highPrecision = false} let center = this.center;
let latitude = center.lat;
let longitude = center.lng;
let scale = this.zoomScale(this.zoom);
let highPrecision = true;
// Calculate scale from zoom if not provided
scale = scale !== undefined ? scale : this.zoomToScale(zoom); // assert(Number.isFinite(latitude) && Number.isFinite(longitude) && Number.isFinite(scale)); const result = {};
const worldSize = TILE_SIZE * scale;
const latCosine = Math.cos(latitude * DEGREES_TO_RADIANS); /**
* Number of pixels occupied by one degree longitude around current lat/lon:
pixelsPerDegreeX = d(lngLatToWorld([lng, lat])[0])/d(lng)
= scale * TILE_SIZE * DEGREES_TO_RADIANS / (2 * PI)
pixelsPerDegreeY = d(lngLatToWorld([lng, lat])[1])/d(lat)
= -scale * TILE_SIZE * DEGREES_TO_RADIANS / cos(lat * DEGREES_TO_RADIANS) / (2 * PI)
*/
const pixelsPerDegreeX = worldSize / ;
const pixelsPerDegreeY = pixelsPerDegreeX / latCosine; /**
* Number of pixels occupied by one meter around current lat/lon:
*/
const altPixelsPerMeter = worldSize / EARTH_CIRCUMFERENCE / latCosine; /**
* LngLat: longitude -> east and latitude -> north (bottom left)
* UTM meter offset: x -> east and y -> north (bottom left)
* World space: x -> east and y -> south (top left)
*
* Y needs to be flipped when converting delta degree/meter to delta pixels
*/
result.pixelsPerMeter = [altPixelsPerMeter, altPixelsPerMeter, altPixelsPerMeter];
result.metersPerPixel = [ / altPixelsPerMeter, / altPixelsPerMeter, / altPixelsPerMeter]; result.pixelsPerDegree = [pixelsPerDegreeX, pixelsPerDegreeY, altPixelsPerMeter];
result.degreesPerPixel = [ / pixelsPerDegreeX, / pixelsPerDegreeY, / altPixelsPerMeter]; /**
* Taylor series 2nd order for 1/latCosine
f'(a) * (x - a)
= d(1/cos(lat * DEGREES_TO_RADIANS))/d(lat) * dLat
= DEGREES_TO_RADIANS * tan(lat * DEGREES_TO_RADIANS) / cos(lat * DEGREES_TO_RADIANS) * dLat
*/
if (highPrecision) {
const latCosine2 = DEGREES_TO_RADIANS * Math.tan(latitude * DEGREES_TO_RADIANS) / latCosine;
const pixelsPerDegreeY2 = pixelsPerDegreeX * latCosine2 / ;
const altPixelsPerDegree2 = worldSize / EARTH_CIRCUMFERENCE * latCosine2;
const altPixelsPerMeter2 = altPixelsPerDegree2 / pixelsPerDegreeY * altPixelsPerMeter; result.pixelsPerDegree2 = [, pixelsPerDegreeY2, altPixelsPerDegree2];
result.pixelsPerMeter2 = [altPixelsPerMeter2, , altPixelsPerMeter2];
} // Main results, used for converting meters to latlng deltas and scaling offsets
return result;
}

对于project_uCommonUnitsPerWorldUnit来说就是计算在精度和纬度上,一度代表的瓦片像素数目。对于project_uCommonUnitsPerWorldUnit2来说这里面用了一个泰勒级数的二阶展开(咨询了下管戈,泰勒级数展开项越多代表模拟值误差越小,这里用到了第二级)主要是在着色器中在`project_uCommonUnitsPerWorldUnit + project_uCommonUnitsPerWorldUnit2 * dy`这里做精度补偿

 
这里也有一些疑点,这里数字也不小,有效位的保留也不多,难道是uniform这种能够保留的有效位多一些?(也可能是转化成了瓦片像素坐标不需要那么高的精度吧。只需要整数的瓦片位,个人猜测可能不对)
gl.uniform3f(this.project_uCommonUnitsPerWorldUnit, distanceScles.pixelsPerDegree[], distanceScles.pixelsPerDegree[], distanceScles.pixelsPerDegree[]);

整体来说使用这种方案解决精度损失引起的抖动问题,为后续的点、线、面、seiya都做了精度基础。
 vec2 project_offset(vec2 offset) {
float dy = offset.y;
// if (project_uCoordinateSystem == COORDINATE_SYSTEM_LNGLAT_AUTO_OFFSET) {
dy = clamp(dy, -., .);
// }
vec3 commonUnitsPerWorldUnit = project_uCommonUnitsPerWorldUnit + project_uCommonUnitsPerWorldUnit2 * dy;
// return vec4(offset.xyz * commonUnitsPerWorldUnit, offset.w);
return vec2(offset.xy * commonUnitsPerWorldUnit.xy);
} // 返回在v3 api中的3d坐标系下的坐标, 采用高精度模式
vec2 project_view_local_position3(vec2 latlngHigh, vec2 latlngLow) {
vec2 centerCoordHigh = project_position(center.xy + center.zw, zoom); // Subtract high part of 64 bit value. Convert remainder to float32, preserving precision.
float X = latlngHigh.x - center.x;
float Y = latlngHigh.y - center.y; return project_offset(vec2(X + latlngLow.x, Y + latlngLow.y)); }

最终效果:

 

WebGL着色器32位浮点数精度损失问题的更多相关文章

  1. 并行计算提升32K*32K点(32位浮点数) FFT计算速度(4核八线程E3处理器)

    对32K*32K的随机数矩阵进行FFT变换,数的格式是32位浮点数.将产生的数据存放在堆上,对每一行数据进行N=32K的FFT,记录32K次fft的时间. 比较串行for循环和并行for循环的运行时间 ...

  2. WebGL 着色器语言(GLSL ES)

    1.类型转换内置函数 转换/函数/描述 转换为整形数/int(float)/将浮点数的小数部分删去,转换为整形数(比如,将3.14转换为3) 转换为整形数/intl(bool)/true被转换为1,f ...

  3. WebGL 着色器偏导数dFdx和dFdy介绍

    本文适合对webgl.计算机图形学.前端可视化感兴趣的读者. 偏导数函数(HLSL中的ddx和ddy,GLSL中的dFdx和dFdy)是片元着色器中的一个用于计算任何变量基于屏幕空间坐标的变化率的指令 ...

  4. IEEE754 32位浮点数表示范围

    6.1浮点数的数值范围 根据上面的探讨,浮点数可以表示-∞到+∞,这只是一种特殊情况,显然不是我们想要的数值范围. 以32位单精度浮点数为例,阶码E由8位表示,取值范围为0-255,去除0和255这两 ...

  5. WebGL着色器渲染小游戏实战

    项目起因 经过对 GLSL 的了解,以及 shadertoy 上各种项目的洗礼,现在开发简单交互图形应该不是一个怎么困难的问题了.下面开始来对一些已有业务逻辑的项目做GLSL渲染器替换开发. 起因是看 ...

  6. OpenGL ES着色器语言之变量和数据类型(二)(官方文档第四章)

    OpenGL ES着色器语言之变量和数据类型(二)(官方文档第四章) 4.5精度和精度修饰符 4.5.1范围和精度 用于存储和展示浮点数.整数变量的范围和精度依赖于数值的源(varying,unifo ...

  7. 着色器语言 GLSL (opengl-shader-language)入门大全

    基本类型: 类型 说明 void 空类型,即不返回任何值 bool 布尔类型 true,false int 带符号的整数 signed integer float 带符号的浮点数 floating s ...

  8. java float double精度为什么会丢失?浅谈java的浮点数精度问题 【转】

    由于对float或double 的使用不当,可能会出现精度丢失的问题.问题大概情况可以通过如下代码理解: public class FloatDoubleTest { public static vo ...

  9. Python之☞float浮点数精度问题

    Python的浮点数损失精度问题(转) 一个简单的面试题: >>>0.1+0.1+0.1 0.2 >>>0.1+0.1+0.1 0.3000000000000000 ...

随机推荐

  1. java关键字-interface

    1:是用关键字interface定义的. 2:接口中包含的成员,最常见的有全局常量.抽象方法. 注意:接口中的成员都有固定的修饰符. 成员变量:public static final 成员方法:pub ...

  2. 如何打造VUCA时代的敏捷型组织?

    王明兰 --原华为.微软创新与转型教练.华为云SaaS产品总监,著名精益&敏捷转型专家 VUCA最早来源于冷战时期,在现代世界意指商业世界越来越不确定性,越来越易变,越来越不可预测,我们已经进 ...

  3. 【转+存】JVM指令集

    jvm指令集: 转载地址:https://www.cnblogs.com/yaoyinglong/p/4300447.html 一.未归类系列A 此系列暂未归类. 指令码    助记符         ...

  4. Unity Shader 屏幕后效果——边缘检测

    关于屏幕后效果的控制类详细见之前写的另一篇博客: https://www.cnblogs.com/koshio0219/p/11131619.html 这篇主要是基于之前的控制类,实现另一种常见的屏幕 ...

  5. 什么是JS跨域请求

    这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被 ...

  6. 该如何真正进入SEO行业?

    今天一个多年的朋友突然问我这个问题,他作为一个seo局外人,感觉SEO挺神秘,我认为要入行就要先了解一个SEO是什么职业,它的工作有那些,然后再考虑怎样进行学习或培训. 一.查看网站状态 seo人员每 ...

  7. HDU 4444:Walk(思维建图+BFS)***

    http://acm.hdu.edu.cn/showproblem.php?pid=4444 题意:给出一个起点一个终点,给出n个矩形的两个对立顶点,问最少需要拐多少次弯可以从起点到达终点,如果不能输 ...

  8. python中的 == 和 is 的区别

    == 比较的是两边的值 is 比较的是两边的内存地址  通过 id()获取内存地址 小数据池:我们使用过的值存储在小数据池中,供其他数据使用. 小数据池仅限于 数字 和 字符串: 数字的小数池范围  ...

  9. Redis 使用C#程序操作Redis

    一.安装操作所需Nuget包 二.写入 redis只是按Key值设置过期时间,不是对value内部的某些值设过期 2.1 string类型 /// <summary> /// 向Redis ...

  10. crontab 中curl命令无法正常执行

    这里所指curl无法执行Url情况是针对带参数的链接,方法体中无法获取参数的值. 比如: */7 * * * * curl http://localhost:8088/backening/sysOrd ...