ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Qt 5.12 + MSVC2017 + OSG 3.4 + osgEarth 2.8 工程配置实战

Qt 5.12 + MSVC2017 + OSG 3.4 + osgEarth 2.8 工程配置实战 简介面向需要在Qt Creator中集成OSGEarth做三维数字地球开发的C工程师这套工程提供可直接编译运行的QtOSGOSGEarth基础框架采用Qt 5.12、MSVC2017搭配OSG 3.4与OSGEarth 2.8省去繁琐的环境配置和工程搭建过程也便于有OSG开发经验的团队快速迁移到Qt Creator。资源以zip形式打包共113个文件、约65MB其中44个dll运行库保证依赖完整42个qm文件用于界面翻译4个h和4个cpp为核心源码3个exe可预览运行效果pro工程文件便于在Qt Creator中修改ui文件对应主窗口界面。目前已有1168人学习下载适合刚接触OSGEarth的开发者或需要快速搭建三维数字地球原型的项目。作者在2060显卡下实测帧率超过150FPS渲染性能经过调优压缩包同时包含obj、pdb、ilk、Makefile等编译中间产物与构建脚本遇到链接或运行问题时可辅助定位。框架保留了主窗口界面与基础交互便于在此基础上继续扩展业务功能运行不成功还可联系作者远程协助整体上手门槛较低。 做 osgEarth 开发的朋友十有八九都绕不开“Qt 做界面 OSG/osgEarth 做三维渲染”这个组合。这个工程配置我前前后后搭了不下十次从 Qt 5.9 一路试到 Qt 5.15最终稳定下来的还是这套经典的 Qt 5.12 MSVC2017 OSG 3.4 osgEarth 2.8。这篇文章把整个配置过程、版本选型的底层逻辑、以及我实际踩过的坑完整过一遍适合正在用 Qt Creator 搭建 osgEarth 工程、或者被各种 DLL 和编译错误折磨的新手参考。先说结论这套组合并不是最新的但它是目前 Windows 平台下兼容性最稳、第三方依赖最容易凑齐的搭配之一。Qt 5.12 是 Qt 5 系列里口碑最好的长期支持版本MSVC2017 生成的二进制与 Windows 10/11 的兼容性极好而 OSG 3.4 osgEarth 2.8 这对搭档在三维 GIS 领域被验证过太多次网上能搜到的资料和解决问题的帖子也最多。对做数字孪生、三维地球、视景仿真的团队来说选这套配置可以少走很多弯路。1. 环境选型与整体思路1.1 这套版本组合解决了什么问题很多人在网上搜 osgEarth 的 Qt 工程配置时会发现不同教程用的 Qt 版本、编译器、OSG 源码版本五花八门直接抄作业往往编译不过。原因在于 osgEarth 的 cmake 配置对 OSG 版本有硬性要求而 OSG 的预编译库又和 Qt 的编译器版本强相关。这套组合之所以稳是因为每个环节都有明确对应关系Qt 5.12.x 官方提供 msvc2017 的预编译包OSG 3.4 在 CMake 配置时能轻松找到 Qt 5.12 的模块osgEarth 2.8 的 CMakeLists 明确支持 OSG 3.4。三个版本互相咬合不用做任何 hack 就能完成整条工具链的配置。适合参考这套方案的人群很明确需要在地球表面叠加 GIS 数据做可视化的开发者做三维态势显示的项目组以及从 OSG 老版本升级上来的团队。如果你只是想随便画个三维场景那直接学 OSG 自带的例子就够了但如果你要在 Qt 窗口里嵌入三维地球、要加载影像和高程、要处理矢量数据这套方案是当前性价比最高的选择。1.2 编译器为什么必须是 MSVC2017这是最容易踩坑的地方。Qt Creator 本身只是个 IDE真正干活的是底层的编译器和调试器。同样一份 Qt 5.12 的库用 MinGW 编译出来和用 MSVC 编译出来的二进制是互不兼容的因为两者的 C ABI 不同链接器格式也不同。OSG 和 osgEarth 在 Windows 下如果我告诉你安装包谁便下那就是坑人的。MSVC2017 这套组合的二进制存在严格的 ABI 绑定OSG 库如果用 MSVC2017 编译Qt 也必须用 MSVC2017 版本的库osgEarth 链接时才能对上符号。OSG 官方提供的预编译包大多是基于 MSVC 生成的MinGW 版本要么自己折腾源码编译要么用别人编译好的第三方包遇到问题几乎没法查。所以标题里的 msvc2017 不是随便写的它是整条链路的基石。遇到版本绑定的问题优先确认三个版本号Qt 版本打开 Qt Creator看左下角 Kit 显示的 Qt 版本必须是 5.12.x 而不是 5.15 或 6.x编译器版本工具链里必须是 Microsoft Visual C Compiler 15.x对应 VS2017而不是 MinGWOSG 和 osgEarth 的库文件必须能对得上。用 dumpbin 或者 Dependencies 工具可以查看 DLL 的导入表确认 osgEarth 链接的是哪个版本的 OSG 和 Qt1.3 版本匹配的底层逻辑为什么不是 Qt 5.15 或 Qt 6我也在 Qt 5.15.2 上跑通过 osgEarth但有两个问题让我退了回来一是 Qt 5.15 开始官方不再提供离线安装包需要通过在线安装器下载对网络环境不友好的团队很麻烦二是高版本的 Qt 对系统 DLL 的依赖更多用 windeployqt 打包出来的程序在某些精简版 Windows 上会出现莫名其妙的运行时错误。Qt 5.12 是最后一个提供完整离线安装包的长期支持版本而且官方明确标注了 msvc2017 二进制的支持安装包里自带 Qt Creator 和调试器一套装完就能用。OSG 3.4 和 osgEarth 2.8 这对组合也是同理。osgEarth 2.8 的 CMake 配置里对 OSG 的最低版本要求恰好是 3.4这意味着你用 3.4 编译 osgEarth 不会出现required version not satisfied的报错。而 OSG 3.4 也恰好是最后一个对 Windows 配置最友好的版本它不需要额外处理 C17 的兼容问题和 MSVC2017 的配合最默契。如果再往上升 OSG 3.6 osgEarth 2.10虽然功能更强但编译时需要更多第三方依赖比如 Proj、GeoTIFF 的版本要求都变了不少人有成功经验也有失败教训新手别拿这个组合练手。2. 编译环境准备与依赖梳理2.1 工具链准备先理清楚需要的软件清单避免装到一半发现少东西Qt 5.12.12选择 MSVC2017 64 位的组件Visual Studio 2017 的 Build Tools只需要 C 编译工具链不需要完整安装 VSCMake 3.16 以上用于编译 OSG 和 osgEarthOSG 3.4 源码和预编译依赖库osgEarth 2.8 源码Git可选用于拉取代码和切换分支有一个点经常被忽略安装 Qt 时一定选中Qt 5.12.12 MSVC 2017 64-bit这个组件同时把Developer and Designer Tools里的 Qt Creator 和 CDB 调试器选上。如果你机器上只装了 VS2019 或更高版本建议别混用因为 MSVC2019 生成的代码和 Qt 5.12 的 msvc2017 库在部分 C 标准库实现上存在差异虽然通常能编译过但 debug 模式下偶尔会出现奇怪的崩溃排查起来非常浪费时间。最舒服的方式是装一个 VS2017 的 Build Tools占的空间不大还能顺带解决 CMake 找不到编译器的问题。2.2 OSG 用源码编译还是找现成包OSG 和 osgEarth 的库可以编译也可以直接下载预编译包。我的建议是如果想长期做开发先源码编译一遍建立自己的库目录。原因很简单预编译包往往带了 zlib、jpeg、png、curl 等一堆第三方 DLL这些 DLL 的版本未必和你系统上的 Qt 相兼容一旦运行时报冲突你连从哪里入手查都不知道。自己编译虽然要花一两个小时但每一步都能控制后面出问题能快速定位。编译 OSG 3.4 时CMake 的关键参数要格外留意# CMake 配置核心选项 BUILD_OSG_EXAMPLESOFF # 不要编译例子节省大量时间 OSG_USE_QTON # 必须开osgEarth 集成需要 Qt 支持 OSG_USE_3MFOFF # 不需要 3D 打印格式支持 WIN32_USE_MPON # 开启多进程编译明显提速 ACTIVE_QT_VERSIONQt5 # 指定使用 Qt5 而不是 Qt4编译前把第三方依赖zlib、libpng、libjpeg、curl的路径填到 CMake 的CMAKE_PREFIX_PATH里。这里有个小坑这些依赖库的位数必须和你编译的目标平台一致64 位 OSG 必须用 64 位的 zlib否则编译时能过链接时报一堆 unresolved external symbol很崩溃。osgEarth 2.8 的编译流程类似唯一多一个步骤是它需要 GDAL、curl 和 sqlite3。GDAL 是最麻烦的如果不想自己编 GDAL可以用 OSGeo4W 的预编译包但是要注意把 GDAL 的 include 目录和 lib 目录都配置到 CMake 的路径里同时把 GDAL DLL 所在目录加入 PATH否则 osgEarth 加载时提示找不到 gdal 相关模块。2.3 统一管理第三方依赖最容易被忽略的是调试符号和库目录的整洁性。我习惯在 D 盘建一个dev_libs文件夹下面再分OSG3.4、osgEarth2.8、3rdparty三个子目录每个子目录里再分 include、lib、bin。这样后续写 Qt 的 .pro 文件时路径写起来非常清爽D:/dev_libs/OSG3.4/include D:/dev_libs/OSG3.4/lib D:/dev_libs/OSG3.4/bin尽量不要把 OSG 和 osgEarth 的 include 目录混合放在同一个目录里因为两者的头文件在命名空间上有交叉混放会导致 osgEarth 引用到错误的 OSG 头文件版本。在实际项目中这两个库的版本更新经常不同步混放目录是日后看起来一切正常但编译产物运行异常的经典诱因。注意所有第三方库的 bin 目录也就是 DLL 所在目录必须加入系统的 PATH 环境变量或者统一拷贝到 app 的 exe 同目录下。这一步不做程序跑不起来的概率很高。3. Qt Creator 工程配置实战3.1 项目文件 .pro 的完整写法当库目录和编译工具链都就绪后接下来在 Qt Creator 里新建一个 Qt Widgets 或 Qt 控制台工程。最关键的是 .pro 文件它关系到编译和链接能否通过。我的一个最小化 .pro 配置如下含注释QT core gui widgets opengl TARGET osgEarthQtDemo TEMPLATE app CONFIG c11 # OSG 与 osgEarth 头文件路径 INCLUDEPATH D:/dev_libs/OSG3.4/include \ D:/dev_libs/osgEarth2.8/include # 需要链接的静态库库文件名会自动转为带 lib 前缀的导入库 LIBS -LD:/dev_libs/OSG3.4/lib \ -LD:/dev_libs/osgEarth2.8/lib \ -losgEarth \ -losgEarthQt \ -losgEarthUtil \ -losgDB \ -losgGA \ -losgViewer \ -losgUtil \ -losg \ -lOpenThreads \ -losgQt # 如果用 Qt5 的 OpenGL 模块需要加上这个 greaterThan(QT_MAJOR_VERSION, 4): QT opengl注意-losg是导入库我这里的 OSG 是 3.4 版本实际生成的 lib 文件名是osg.lib、osgViewer.lib等如果你下载的预编译包是不同命名规则需要对照 lib 目录里的实际文件进行修改。debug 模式下有些库会追加一个d后缀比如osgD.lib、osgEarthD.lib如果写错了链接阶段会提示找不到osg.obj之类的错误。正确做法是给 debug 和 release 分别写不同的配置块CONFIG(debug, debug|release) { LIBS -losgEarthd -losgEarthQtd -losgd } else { LIBS -losgEarth -losgEarthQt -losg }3.2 几处容易忽略的配置细节G 增量编译选项MSVC 的/Z7选项需要保持默认不要为了减小 pdb 文件大小而关闭osgEarth 在某些代码路径上依赖调试符号来处理运行时异常关闭后崩溃信息会完全不可读。OUTPUT_PATH如果 debug 和 release 的输出目录不一致建议都指定为同一个目录否则拷贝 DLL 时容易漏掉其中一个版本。UI 文件与 MOC 文件如果你在界面里用了继承自osg::ViewerWidget的组件不要忘记在 .pro 里加入HEADERS这样 Qt Creator 才会自动运行 moc 生成元对象代码。漏掉的话会报undefined reference to vtable一类的链接错误。预编译头的处理如果工程开了PRECOMPILED_HEADER务必把 osg 相关的头文件放在#include stdafx.h之外否则同一份 pch 在不同编译单元里会因为头文件包含顺序不一致出现奇怪的内存布局问题。第三方 DLL 路径最后不要忘了在 Qt Creator 的Run环境设置里把 OSG 和 osgEarth 的 bin 目录加入 PATH。在 Qt Creator 里运行时默认是不会带系统 PATH 之外的自定义目录的。加了之后F5 一键运行才能正确加载库。3.3 第一个 osgEarth 嵌入 Qt 的代码骨架这里给出一个最简可运行的代码结构它有点示范性可以很快帮你验证整个环境是通的。主窗口类中通过osgQt::GLWidget创建 OpenGL 上下文承载 osgEarth 场景// main.cpp #include QApplication #include QMainWindow #include osgViewer/Viewer #include osgEarth/MapNode #include osgEarth/Map #include osgEarthDrivers/xyz/XYZOptions #include osgEarthQt/ViewerWidget #include osgEarthUtil/EarthManipulator int main(int argc, char** argv) { QApplication app(argc, argv); // 创建一个 osgEarth 地图节点 osgEarth::MapOptions mapOpt; osgEarth::Map* map new osgEarth::Map(mapOpt); // 添加一个 XYZ 影像图层例如 OpenStreetMap 瓦片 osgEarth::Drivers::XYZOptions xyz; xyz.url http://tile.openstreetmap.org/{z}/{x}/{y}.png; map-addImageLayer(new osgEarth::ImageLayer(OSM, xyz)); osgEarth::MapNode* mapNode new osgEarth::MapNode(map); // 创建 Viewer 并设置操纵器 osgViewer::Viewer viewer; viewer.setSceneData(mapNode); viewer.setCameraManipulator(new osgEarth::Util::EarthManipulator()); // 把 Viewer 包成 Qt 控件 osgQt::ViewerWidget* widget new osgQt::ViewerWidget(viewer); widget-setGeometry(100, 100, 800, 600); widget-show(); return app.exec(); }这里有几个细节要说明osgQt不是osgViewer自带的 Qt 模块在 OSG 3.4 中必须编译时打开OSG_USE_QT选项才能生成osgQt库。如果你编译 OSG 时没开这个选项也会导致找不到osgQt头文件或库文件。osgEarth::MapNode的创建方式有多种上面示例是用代码动态添加图层。对于更复杂的场景通常会丢一个.earth文件到工作目录再直接加载模型。比如osg::ref_ptrosgEarth::MapNode mapNode osgEarth::MapNode::load(map.earth);osgQt::ViewerWidget在 debug 模式下析构时偶尔会崩溃这是 OSG 3.4 与 Qt 5.12 在事件循环销毁顺序上的已知小问题解决方法是把viewer和mapNode都声明成局部变量并确保widget先于viewer销毁。不处理也能跑但退出程序时可能多一个报错框。4. 常见问题与排查技巧实录4.1 no Qt platform plugin could be initialized 的完整解法这个热词在 Qt 运行时错误里出现频率超高。程序在 Qt Creator 里按 F5 能正常弹出窗口可一旦脱离 Qt Creator 直接双击 exe就弹出这样一个对话框中文环境下是无法初始化 Qt 平台插件紧跟着可能还有一句reinstalling the application may fix this problem。这个问题的根源是 Qt 的platforms平台插件目录没有被正确找到。当你编译 Qt 程序时Qt 库位于安装目录运行程序时Qt 会通过可执行文件路径下的platforms目录来搜索qwindows.dll。如果你直接复制 exe 出来没带上 Qt 安装目录下plugins/platforms/qwindows.dll那自然初始化不了。解决办法也不难在 exe 同目录下创建platforms文件夹把 Qt 安装目录下plugins/platforms里的qwindows.dll复制进去或者直接调用windeployqt.exe自动部署它会把 Qt 所有需要的 DLL 和插件按目录结构打包装好在 Qt Creator 的 Run - Run Environment 里设置QT_QPA_PLATFORM_PLUGIN_PATH指向 Qt 的 plugins 目录仅用于开发调试定位最终发布时还是建议用 windeployqt 打齐如果再配合 osgEarth这种平台插件找不到的问题在集成环境中更容易被误读。因为 osgEarth 的第三方依赖里可能带了它们自己的 Qt DLL如果你装过某些 GIS 工具导致 exe 运行优先加载了错误的 Qt 库间接让平台插件找不到。检查方法是用 Dependencies 工具看加载的是哪个Qt5Core.dll如果路径不在你的 Qt 安装目录就是版本污染了。4.2 运行时报 DLL 加载失败的排查思路osgEarth 工程的 DLL 依赖关系非常复杂。OSG 自身就有一堆osg80-osg.dll这样的带版本号 DLLosgEarth 还依赖 GDAL、curl、sqlite3以及可能作为插件的图像格式库。加载时只要缺少其中任意一个程序要么直接崩溃要么在控制台打印一段找不到指定的模块的错误。排DLL问题时我常用三步法先用 Dependencies 打开 exe看红色标注的缺失 DLL 是哪些再检查 PATH 环境变量里是否包含所有第三方库的 bin 目录最后对比 debug 和 release 版本是否混用第三步很容易被忽略因为在 Qt Creator 里debug 模式默认会把 debug 版本的 DLL 拷到构建目录但如果你的 OSG 库是用 release 方式编译的debug 程序链接它会报一个警告LNK4099 之类程序照样能跑一旦运行到某个特定渲染分支就会崩溃。解决的思路是分配好两个独立的库目录比如osg_rtrelease和osg_dbg在 .pro 里根据CONFIG(debug, debug|release)去引用对应的目录不要偷懒用同一个。还有一个经验如果 exe 的运行目录里出现多个版本的osgEarth.dll程序会不知所措。很多时候 osgEarth 的 earth 文件里引用了自定义的第三方插件这些插件的 DLL 也会依赖 OSG 库正好把新版本的 OSG 库带进来程序加载时就会按 DLL 搜索顺序先是 exe 所在目录再是 PATH去加载导致同一进程里同时存在新老两套 OSG 库最终在内存分配上发生冲突。推荐的做法是始终把 exe 目录里所有 OSG 相关 DLL 盯紧不要随意把开发环境里的库全部拷贝进去。4.3 头文件与库版本错乱的典型表现如果你装过其他依赖 OSG 的软件电脑上很可能存在多套 OSG 头文件和库目录。编译器在链接时如果优先找到了错误版本最常见的症状是编译时报osgEarth/MapNode头文件找不到或者某个类和函数没有定义比如MapNode没有getMap()方法链接时报大量 unresolved external symbol这种问题排查上有一个很反直觉的点即使你把 INCLUDEPATH 写对了系统或者第三方工具仍然可能通过环境变量干扰查找顺序。比如安装了某个 GIS 工具后它已经把旧版 OSG 的 include 和 lib 目录写进系统 PATH 了CMake 和 Qt 的 qmake 在某些配置下会优先读取系统 PATH 里的内容来寻找依赖。检查思路是这样打开 Qt Creator 的Projects - Build Environment把 PATH 变量完整展开看里面有没有你不知道的路径。如果有先删掉。编译时可以在 .pro 里强制指定优先目录INCLUDEPATH D:/dev_libs/OSG3.4/include $$INCLUDEPATH LIBS -LD:/dev_libs/OSG3.4/lib $$LIBS这样能保证自定义路径先于系统路径被找到。在 .pro 里用$$INCLUDEPATH而不是直接覆盖标准路径是为了防止影响 Qt 自身头文件的查找。4.4 粒子效果与其他渲染特性的集成提醒热词里有osg粒子效果这里顺带提一句。osgEarth 工程里加粒子系统时要特别注意它和 osgEarth 渲染状态集StateSet的交互。我遇到过粒子特效在普通 OSG 场景里正常、一放进 osgEarth 场景就变透明的怪事最后排查是 RenderBin 顺序的问题。解决方案是把粒子的setRenderBinDetails数值调大比如设到 15 以上确保它在场景地形渲染之后再绘制。另外 osgEarth 的默认深度缓冲设置对粒子叠加大概率有影响如果粒子被地形遮挡或半透明闪烁可以尝试关掉地图节点的深度测试或者改用osgEarth::Util::SkyNode提供的 order 机制统一管理渲染顺序。4.5 QML 还是 Widgets界面框架的另一个选择如果你用的是 Qt Quick / QML 来做界面那这套配置要加一点额外的桥接。Qt 5.12 对 QML 与 OpenGL 的叠加支持不如 Widgets 方便osgQt 的ViewerWidget是继承自 QWidget 的无法直接嵌入 QQuickItem。解决方式是用QQuickRenderControl把 OSG 的渲染输出绑到纹理上再在 QML 里用Image或ShaderEffect显示。这个方案能跑通但触摸事件、多线程渲染和 QML 的渲染线程之间容易出同步问题。我的实际建议是如果不是项目强制要求 QML三维渲染还是用 Widgets 框架更省心。5. 工程维护与后续升级建议5.1 用 windeployqt 实现一键打包Qt 程序发布时很多新手会手工复制 DLL结果漏掉一堆。Qt 官方给的 windeployqt 工具可以自动化处理大部分工作只需要在编译好 release 后打开命令行执行D:/Qt5.12.12/5.12.12/msvc2017_64/bin/windeployqt.exe --release --no-translations ./build/osgEarthQtDemo.exe执行完毕后exe 同目录下会自动生成 Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll 以及 platforms、styles 等插件目录。注意 windeployqt 只识别 Qt 自身的依赖不会帮你拷贝 OSG 和 osgEarth 的 DLL这些还是要手动把bin目录下的文件复制过去。有个小技巧把 OSG 和 osgEarth 的 bin 目录整个复制到 exe 的同一目录下不用单独建子目录这样加载顺序最稳并且能避免 PATH 环境变量被其他程序干扰。5.2 版本升级时怎么减少迁移成本如果后续要升级到 Qt 5.15 或 Qt 6首先要确认 OSG 3.4 的第三方依赖是否兼容新 Qt。我的做法是先升级 OSG 到 3.6 及以上因为 osgEarth 2.10 的角色较多整体 API 改动也不小。升级前一定先查 osgEarth 的 CHANGES 文件明确它所需的 OSG 最小版本。另一个办法是保留两套构建目录一套用于稳定维护一套用于新版本验证不要在同一工程里直接切换编译器或 Qt 版本去强编因为即使源码不需要改动CMake 缓存里也会残留旧路径排查起来特别费劲。5.3 源码版本管理中的库目录策略最后再说一个容易被忽略的点OSG 和 osgEarth 的库文件体积不小不要把它们直接提交到 Git 仓库里。我项目里常见的最优做法是写一个setup_env.bat在里面定义好OSG_DIR、OSEARTH_DIR、THIRDPARTY_DIR等环境变量然后在 .pro 文件里引用这些变量。这样团队里每个人克隆代码后只需要修改一个 bat 文件里的路径就能正常编译不需要改动工程文件。既避免了仓库臃肿也让代码迁移到其他机器时更方便。依赖库放在 D 盘下的某个固定路径同时所有机器统一用相同的相对目录结构是减少在我电脑上能跑问题的一招。如果你经常在多台电脑之间切换建议把这个路径约定好比如统一在C:/dev或D:/dev下建库目录。团队协作时每个人的库目录保持一致能省掉非常多不必要的沟尤其是当你和同事用不同盘符时。最后再分享一个和 osgEarth 相关的小技巧在调试 osgEarth 的 elevation 和影像加载问题时http://tile.openstreetmap.org 这类在线瓦片源偶尔会加载失败不是因为代码有问题而是网络或瓦片源本身不稳定。测试时尽量用本地生成的 earth 文件或离线缓存瓦片来排除网络因素。我自己操作时会在map.earth里配置多个候选源并且开启cache选项这样即使网络波动测试体验也不会被打断。如果遇到瓦片黑白格或缺失多半是瓦片缓存路径权限不足把osgEarth的缓存目录改到当前用户的可写目录下就能解决。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进