精度丢失导致扭曲与抖动

在这里插入图片描述
最近工作上遇到了精度问题导致了模型整体偏移+模型抖动的问题!是数据的问题准确说是:精度问题导致的,最好用相对坐标系别用绝对的,相对相机或者项目初始配置一个左下角点,然后所有的顶点减去这个左下角点,最好是用相对相机的,这样不用管顶点多大,最后都是小坐标 (工程人员干图形都是糙快猛,啥都不懂 数据用double,不知道GPU是float 精度损失常事了)

相机对数深度缓冲:(解决Z-fight)

(首先猜测是否是Z-fight 就研究了一下logarithmic-depth 后来不是这么会事,抖动和z关系不大。闪烁才有可能是z-fighting,z-fighting和您这个大坐标抖动不是一个问题,z-fighting的核心问题是depth test的时候精度不够了) 这个有点类式于这吧顶点位置的数值太大gpu运算的精度不够(不管是什么平台什么引擎OPENGL 、DX、VK GPU 的精度是由GPU硬件决定的现在各大厂商都不支持double只支持float类型)会导致顶点位置错乱 相机有个对数就是根据相机的近截面显示的详细一些,远截面粗糙一点,使用对数深度缓存可以在一定程度上解决z冲突(Z-Fighting);原理:对数就是先急后缓就是离这相机近的多渲染,远的少渲染,就是z-buffer里面,小的值渲染力度大,渲染的更精细不用对数,就是线性;
在这里插入图片描述
对数相机解决Z-fight 缺陷,depth buffer用对数缓存其实并不能解决z-fight问题,只是缓解一下!真要彻底解决,得上depth partition,z-fighting的核心问题是depth test的时候精度不够了,比如在(0, 100000]的远近平面范围内,两个相距10米的平面,也会fighting,因为转换到深度值之后,float的精度不够。所以一劳永逸的方法是做一次depth partition,也就是在中间切一刀,把视锥体分成前后两个,先渲染后面那个,然后不清除缓存,再渲染前一个,因为后一个肯定被前一个遮挡,所以这个过程中不需要Z-Test。当然切分更多块也没问题,比如您想从银河系中心一直冲到地球上自己家的床上,这么切主要是损耗一些效率,因为必然有些东西要渲染两次。

threejs对数相机
cesium中的对数相机
Hybrid Multi-Frustum Logarithmic Depth Buffer

精度丢失问题

后来发现真正的问题是精度丢失问题;扭曲是数据传输过程中精度丢失;抖动是应用程序阶段到集合阶段MVP中的M与V的问题!float类型有效位十进制是8位,其中在二进制中1位符号位,8位指数位作为科学计数法,23位尾数作为有效数;
使用 OpenGL 和 Direct3D 等图形 API,图形处理单元 (GPU) 在内部对大部分遵循IEEE 754 规范
的单精度 32 位浮点数进行操作 。单精度值通常被认为有七个精确的十进制数字;因此,随着您的数字变得越来越大,这些数字的表示越来越不准确。

虽然gpu现在也是可编程状态,但是仅只支持float类型,所以向显卡里面传递数据的时候总会有精度的损失,在cesium里面尤为明显。地球那么大,存储球皮上面的一个坐标所用的字符长度肯定会很大,这是其中的一个原因也是比较重要的一个原因 。比较常规的方式使用局部坐标的方式,可以理解为先确认一个基准点,其他要绘制的点都是相对于这个基准点的。所以存储坐标点的精度都会比较小。再利用平移和旋转把这些点放到该放的位置,平移和旋转用矩阵的方式表示。
cesium里面使用两个float类型,float-float 类型来表示double这也是一个显卡里面一个比较好的解决方案,可以使用static 的VBO这个在opengl里是渲染最快的!

RTC :

Rtc相对于中心点;把mesh的顶点坐标系统一在相对中心点下,这时顶点数据相对不变位一个相对值!将mesh的矩阵做Transform相对于中心点center(不想对于原点(0,0,0))
优点:重算Matirx相对于成百上千的的顶点来说成本很低;
缺点:每次camera移动都需要重新计算Matirx;(计算量少可忽略不计)

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

RTE:相对于视点view

1、每一个顶点都减去观察者位置;
2、提交的变换矩阵Matrix是有旋转二没有平移;
缺点:顶点数量太多消耗大量的时候在CPU的计算,而不是GPU;传输的是非static VBO;
引擎的应用情况: Cesium,但解决的并不好

方法三 RTE与double拆分:

解决RTE的缺点,提高在GPU上的精度 一个double用两个float表示,在cpu(应用程序阶段)编码为2个float,将两个float传到GPU中;

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);
}

业界处理的方法:

关于RTE这个问题,Cesium解决的并不好,业界的做法是实际上应该对数据做预处理,把数据组织成块,而不是把double数据拆分两个float再传入Shader,顶点数据不应该超出float来表达。用Tile来组织顶点,然后把Tile的中心坐标在CPU端做RTC,才能有效降低计算量。对于RTC有CPU端的RTC和GPU的RTC。Cesium在shader中做double,可能主要是想在shadere中做逐像素的投影变换。然而实际上这种工作应该在数据预处理之前就做好。就算是做实时投影变换的话,在CPU端也可以不做逐顶点变换,用采样的方式,做少量顶点变换,其他顶点相对于这些采样点做加权平均更好。虽然精度会降低,但其实应用的时候没太大影。因为投影变换本身就是非线性变换,所以cesium为了控制精度在Shader中逐像素算了。
如果采样插值的方式,挑少量控制点,在CPU端对每个Tile计算,然后每个顶点根据插值点作为控制点进行加权平均,也算是一种CPU投影方式。这样的结果其实精度可控,如果精度不达标就增加采样控制点就行了。

参考:

大坐标精度丢失原理
cesium大坐标的做法
精度损失和抖颤

猜你喜欢

转载自blog.csdn.net/chenweiyu11962/article/details/123123033
今日推荐