float32精度可以到小数点后几位?_OSG的双精度浮点数应用攻略

a3a4731d6cc328cd2ab5c804fa6b2fb2.png
双精度顶点坐标着色器渲染RenderDoc抓帧图

大坐标问题:

实际渲染建模中,有时候会遇到顶点坐标特别大的情况,比如一条直线公路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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是在osg中调用mysql的基本步骤: 1. 安装mysql的C++ Connector库 可以从mysql官网下载安装,也可以使用包管理工具进行安装。 2. 在osg程序中包含mysql的头文件 在需要使用mysql的地方包含头文件:<mysql_connection.h>、<driver.h>、<exception.h>等。 3. 连接mysql据库 使用mysql C++ Connector提供的接口,连接mysql据库。示例代码如下: ``` sql::Driver* driver; sql::Connection* conn; driver = get_driver_instance(); conn = driver->connect("tcp://127.0.0.1:3306", "username", "password"); conn->setSchema("databasename"); ``` 上面的代码中,需要替换掉"username"、"password"和"databasename"为对应的mysql用户名、密码和据库名。 4. 执行SQL语句 使用mysql C++ Connector提供的接口,执行SQL语句。示例代码如下: ``` sql::Statement* stmt; sql::ResultSet* res; stmt = conn->createStatement(); res = stmt->executeQuery("SELECT * FROM table_name"); while (res->next()) { std::cout << res->getString("column_name") << std::endl; } delete res; delete stmt; ``` 上面的代码中,需要替换掉"table_name"和"column_name"为对应的mysql表名和列名。 5. 关闭连接 使用mysql C++ Connector提供的接口,关闭连接。示例代码如下: ``` conn->close(); delete conn; ``` 完整的示例代码如下: ``` #include <iostream> #include <mysql_connection.h> #include <driver.h> #include <exception.h> int main() { try { sql::Driver* driver; sql::Connection* conn; driver = get_driver_instance(); conn = driver->connect("tcp://127.0.0.1:3306", "username", "password"); conn->setSchema("databasename"); sql::Statement* stmt; sql::ResultSet* res; stmt = conn->createStatement(); res = stmt->executeQuery("SELECT * FROM table_name"); while (res->next()) { std::cout << res->getString("column_name") << std::endl; } delete res; delete stmt; conn->close(); delete conn; return 0; } catch (sql::SQLException& e) { std::cout << "SQLException: " << e.what() << std::endl; return 1; } } ``` 上面的代码中,需要替换掉"username"、"password"、"databasename"、"table_name"和"column_name"为对应的mysql用户名、密码、据库名、表名和列名。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值