![a3a4731d6cc328cd2ab5c804fa6b2fb2.png](https://i-blog.csdnimg.cn/blog_migrate/b6bfecd717dfcfca7a5aadf65791e0f0.jpeg)
大坐标问题:
实际渲染建模中,有时候会遇到顶点坐标特别大的情况,比如一条直线公路100公里,按米为单位建模,顶点坐标就可能达到10 0000。
更比如GIS建模,或者按地理投影的平面模型,坐标更是可能在百万单位级别。
在这样的顶点坐标情况下,32位浮点数有效位数有限,运算过程中无法保持一定位数的小数计算准确,直接渲染会发生单精度浮点精度损失,导致渲染失真,一条直线可能被渲染成一条折线,一个圆环可能渲染出多个锯齿。并且两个本来应该衔接的模型也可能会发生错位。并且随着视角的变动,精度损失也会发生变化,渲染失真产生的缝隙可能会一会大一会小。
由Cesium官方文档所引用的一篇文档[1],可以引出多个解决方案,以下文档以OSG为例详述其中两个方案的具体实施方法。同样,在该文档中完整的讲述了浮点数精度导致的GIS渲染失真的问题,以及解决思路。
解决方案一:加入偏移矩阵,分离顶点的大部和小部。
比如原有顶点(100 0000.2112, 200 0000.249, 0.0),由于顶点数值大,小数点后几位容易损失精度。所以我们可以分离为osg::Vec3(0.2112, 0.249, 0.0)与位移矩阵(100 0000, 200 0000, 0.0)。
比如源代码为:
osg
优化后的代码为:
osg::Vec3d pt;
osg::Vec3d base = BASE_VECTOR;
osg::Geometry * geo = new osg::Geometry;
osg::Vec3Array * vertex = new osg::Vec3Array;
vertex->push_back(pt - BASE_VECTOR);
geo->setVertexArray(vertex);
osg::MatrixTransform * trans = new osg::MatrixTransform(osg::Matrix::translate(BASE_VECTOR);
trans->addChild(geo);
该优化方法代码上能够测试通过,效果良好,能够消除渲染中的锯齿失真,以及模型错位等。
原理上我并没有仔细研究清楚,推测是由于矩阵的结合顺序,假定为透视投影,渲染视图想要仔细观察目标顶点所在区域,则视图矩阵必然会带有接近-BASE_VECTOR的位移,视图矩阵先与位移矩阵结合,两个BASE_VECTOR的正负位移相互抵消,于是剩下的就是小部的顶点坐标了。
该方法能够解决小规模网格组的远距离建模,并且osg对于此方案支持较好,能够直接支持拾取,计算包围盒等一系列osg内置功能。
缺点在于对于大规模的连续网格组,这个方法用起来还是会比较费力,建模时需要拆分模型并且分离大部小部。
解决方案二:直接使用双精度顶点,通过着色器绘制渲染
顶点数值不变,也不进行分离,直接绑定到着色器,并且在着色器中也使用双精度投影矩阵和视图矩阵,直接渲染出最终图像。
由于osg能够支持双精度浮点数着色器输入,但是不支持双精度顶点数组的包围盒,拾取等计算,需要复制一份顶点,降精度到单精度绑定到顶点。
比如原有代码:
osg::Vec3d pt;
osg::Vec3Array *vertexf = new osg::Vec3Array;
vertexf->push_back(pt);
优化为:
osg::Vec3d pt;
osg::Geometry *geo = new ...
osg::Vec3dArray * vertexd = new osg::Vec3dArray;
vertexd->setPreserveDataType(true);
vertexd->push_back(pt);
geo->setAttributeArray(4, vertexd);
geo->setVertexArray(Vec3dArrayToVec3Array(vertexd));
setPreserveDataType这个接口用于保持顶点数组的双精度类型,否则传入VBO时会被自动转为单精度浮点。
同样带上着色器:
#version 400
要注意双精度顶点着色器支持需要GLSL 400以上版本,双精度顶点只能和双精度矩阵进行运算,并且计算完成后需转换为单精度向量以传入单精度的最终屏幕坐标。
这个方法,实际使用时能够满足大坐标直接输入顶点渲染的效果,同样解决渲染失真和模型错位。相对前一个方法,建模起来更方便,不需要考虑节点层级,数值分离。但是由于osg的拾取和计算包围盒等功能是基于降低精度的单精度顶点,会有一定误差。后续可以尝试使用像素拾取解决拾取误差,或者进一步修改osg代码添加完整的双精度顶点支持。
优化细节:
这两个方法,有一个共性的问题需要注意,计算过程精度损失问题。在传入顶点数组之前,尽量使用双精度浮点计算建模,由于浮点精度损失在显卡中还是在CPU中都是一样会发生的,所以在渲染外的计算过程与建模过程,要直接使用双精度浮点以防止误差的产生与累计。
以及,直接使用双精度顶点着色器渲染时,要防止使用双精度4维向量,实际过程中,对颜色数组使用了双精度4维向量,此时数据量大时产生了严重的卡顿,换成单精度4维向量就解决了此问题,和使用单精度直接渲染时一样的渲染速度。猜测和显存带宽有关,一般现在的游戏显卡带宽为192bit,刚好是一个双精度3维向量(顶点)的数据长度。然而一个双精度4维向量数据长度为256bit,超出了一般游戏显卡的显存带宽,即一组数组会消耗双倍的显存存取指令。
并且,对于OSG这种默认为使用OpenGL显示列表的图形引擎,推荐开启VAO VBO并且关闭显示列表以提高性能,多使用现代的OpenGL接口。
引用文档:
[1] https://help.agi.com/AGIComponents/html/BlogPrecisionsPrecisions.htm