
简介面向基于Qt的OsgEarth三维数字地球开发者这份代码把OSG 3.4与OsgEarth 2.8成功集成到Qt Creator 5.12与MSVC2017环境中构成一套可直接运行的完整工程。工程内部已经封装好主窗口与渲染交互逻辑无需额外组装即可编译启动适合作为三维地球应用的基础框架。资源共113个文件压缩包约65MB其中44个DLL解决运行库依赖42个qm为Qt界面翻译文件4个cpp与4个h文件保留核心源码3个exe是可执行程序同时配套obj、pdb、pro、ui、Makefile与debug/release等工程文件便于二次开发、排错和配置调整MOC生成文件也一并保留方便理解Qt信号槽机制与OsgEarth渲染线程的配合。作者在2060显卡上实测帧率超过150能够支撑流畅的交互浏览大幅降低OsgEarth与Qt混编时的环境搭建门槛。已有1168人学习下载适合需要快速构建高帧率数字地球原型的中高级Qt与OSG开发者。 刚把一套 osgEarth 工程从别的机器迁移到 Qt Creator 里环境是 Qt 5.12、编译器选的 MSVC2017、OSG 3.4 osgEarth 2.8折腾了大概一个周末把该踩的坑基本都踩了一遍。今天把这套环境的搭建过程、工程配置思路、还有那些“文档里不写但你必须知道”的细节整理出来给正好想用 Qt OSG osgEarth 做三维GIS或仿真开发的朋友做个参考。先说你拿到这套组合最关心的几个问题这套东西能不能跑配置起来有多麻烦为什么网上有人用 MinGW 也能编译有人却坚持用 MSVC以及那个最常见的 no qt platform plugin could be initialized 报错到底怎么根治。这篇博文就是从零开始、用 MSVC2017 编译器把 osgEarth 工程跑起来的完整记录步骤和坑都写清楚了。1. 版本选型与整体设计思路1.1 为什么是 Qt 5.12 MSVC2017 OSG 3.4 osgEarth 2.8这套组合看起来像是“老古董”但在工业项目和科研场景里非常常见。我先说结论Qt 5.12 是 Qt 5 系列里稳定性口碑最好的一代MSVC2017 对应的是 Visual Studio 2017 的运行时OSG 3.4 是 OpenSceneGraph 的经典稳定分支而 osgEarth 2.8 是跟 OSG 3.4 搭配最顺的版本。这四者的组合不是随便拼的而是经过社区大量项目验证过的“黄金搭配”。很多人上来就问“能不能用 Qt 6 OSG 3.6 osgEarth 3.x”技术上当然可以但你要面对的是编译链脱节、第三方依赖库找不到匹配版本、插件加载方式变了等一系列问题。在项目交付场景下稳定压倒一切所以这套老组合至今仍有大量存量项目和商业产品在用。如果你手头有现成的数据、插件、模型资源也是基于这套组合生成的那就不用犹豫直接用这个版本搭配。还有一点值得注意编译器必须统一。Qt 库、OSG 库、osgEarth 库、以及你的应用程序四者的 ABI二进制接口必须一致。Qt 5.12 官方提供的有 MSVC2015、MSVC2017 等预编译包OSG 和 osgEarth 你如果是自己用 CMake 编译的也要选对应的 Visual Studio 生成器。混用不同编译器的库最常见的后果就是链接时一堆无法解析的外部符号或者运行时崩溃在某个莫名的内存地址上。1.2 MSVC 和 MinGW 的本质区别既然标题里明确写了“编译器 msvc2017”我得把这事儿说透。Qt 官方安装包会同时提供 MinGW 和 MSVC 两套工具链很多人图省事装了 MinGW 版本结果后面调用某些 Windows API、接入第三方 SDK 时各种受挫。MSVC 就是 Microsoft Visual C 编译器它的运行库是 Windows 系统自带的 Universal CRT 和 VC RuntimeMinGW 是 GNU 编译器在 Windows 下的移植版用的是开源运行库。两者生成的二进制文件不能混用你写代码时包含的头文件、链接的 .lib 文件都必须跟最终执行时加载的 .dll 来自同一套编译链。对 OSG / osgEarth 这套库来说还有更现实的原因osgEarth 依赖的 GDAL、CURL、SQLite 等第三方库在 Windows 上大多只有 MSVC 版本的预编译包。你要用 MinGW 就得全部自己折腾源码编译工作量瞬间翻好几倍。而且 OSG 官方提供的 Windows 预编译包也是 MSVC 版本的。所以做这套开发老老实实走 MSVC 是性价比最高的路。2. 开发环境准备与库编译要点2.1 Qt 5.12 安装时的组件选择装 Qt 的时候千万别无脑全选也不要只选一个“Qt 5.12.12”就完事。打开 Qt 安装器的组件树在 Qt 5.12.12 这一层下面你会看到 MSVC 2017 32-bit、MSVC 2017 64-bit 这两个关键选项。如果你的内存大于 4GB、模型数据比较大务必选 64-bit 版本我这边就是用 64 位的。另外强烈建议勾选 Qt Debug Symbols 和 Qt Sources前者让你在调试时能进入 Qt 源码后者在排查问题时可以直接看 Qt 的实现。别嫌占磁盘空间Debug 阶段你会感谢这两个选项的。还有 Qt Creator 本身不用额外装Qt 安装包自带。不过装完以后建议到“工具 → 选项 → Kits → 编译器”里确认一下 MSVC2017 的检测状态。有时候你已经装了 VS2017但 Qt Creator 没自动识别需要手动添加cl.exe的路径。添加方法很简单编译器选项卡里点“添加 → MSVC”编译器路径填C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64\cl.exe具体的版本号路径以你机器为准。这里容易犯的错是填了 Hostx86 的路径导致 64 位目标编译不了。2.2 OSG 3.4 与 osgEarth 2.8 的编译准备OSG 和 osgEarth 涉及到的第三方依赖比较多如果你是第一次编译建议先理顺依赖顺序第三方库 → OSG → osgEarth。第三方库里最核心的是 GDAL、CURL、SQLite、ZLIB、PNG、JPEG、TIFF 等跟地形和影像读写相关的库。我不建议你用 vcpkg 一次性全装因为 vcpkg 默认的 OSG 版本可能跟 osgEarth 2.8 的接口不匹配。更可控的做法是手动下载源码逐个用 CMake 配置编译OSG 3.4 源码直接从 GitHub 的 OpenSceneGraph 仓库拉取OpenSceneGraph-3.4.1标签osgEarth 2.8 源码拉取osgEarth-2.8分支注意 osgEarth 2.8 对 CMake 的最低版本有要求建议 CMake 用 3.15 以上。编译时几个关键配置项CMAKE_PREFIX_PATH D:/dev/Qt/5.12.12/msvc2017_64 # 让 osgEarth 能发现 Qt CMAKE_BUILD_TYPE Release OSG_USE_QT ON如果你在 osgEarth 的 CMake 配置界面里找不到 Qt 相关选项大概率是CMAKE_PREFIX_PATH没指对。Qt 的 MSVC2017_64 目录下应该有lib/cmake/Qt5这样的子目录CMake 才能通过find_package(Qt5)找到 Qt。2.3 编译时的核心参数选择编译 OSG 时有几个参数会直接影响后续使用体验我按优先级排一下第一CMAKE_CONFIGURATION_TYPES 设成 Release;Debug。不要只编 Release调试时你会需要 Debug 版本来定位崩溃问题。第二OSG_USE_QT 一定要开 ON否则 osgEarth 的 UI 集成组件比如 osgEarthQt编不出来。第三BUILD_OSG_PLUGINS 保持默认全开OSG 的插件机制是靠动态库实现的少了某个插件就意味着某类模型或图片加载不了。编译时间上OSG 全量编译在 8 核机器上大约要 20-30 分钟osgEarth 依赖少一些大约 5-10 分钟。编译完以后记得把生成目录里的bin路径加到系统 PATH或者至少在你的工程运行配置里把 PATH 指过去。我的做法是直接在系统环境变量里加D:/dev/OSG/OpenSceneGraph/bin D:/dev/osgEarth/install/bin这样不管是命令行还是 Qt Creator 里跑都能找到对应的 DLL。3. Qt Creator 下的 osgEarth 工程搭建实操3.1 创建工程与 .pro 文件编写打开 Qt Creator新建项目时选“其他项目 → 空的 qmake 工程”就行。我一般不勾选“生成主窗口”之类的模板因为 OSG 渲染窗口的嵌入方式跟普通 QWidget 稍有不同手写反而更清晰。工程的 .pro 文件是整个配置的核心我的基础配置长这样QT core gui widgets opengl CONFIG c11 TARGET OsgEarthDemo TEMPLATE app DEFINES QT_DEPRECATED_WARNINGS DEFINES WIN32 INCLUDEPATH D:/dev/OSG/OpenSceneGraph/include \ D:/dev/osgEarth/install/include \ D:/dev/Qt/5.12.12/msvc2017_64/include LIBS -LD:/dev/OSG/OpenSceneGraph/lib \ -LD:/dev/osgEarth/install/lib \ -lOpenThreads \ -losg \ -losgDB \ -losgGA \ -losgViewer \ -losgEarth \ -losgEarthUtil \ -losgEarthQt这里有几个细节要提醒你lib 名称不要带 .dll 或 .lib 后缀也不要带osg130之类的版本号。qmake 会根据-l参数自动去LIBS指定的目录里找对应名称的 .lib 文件。如果你编译 OSG 时改了库命名规则比如加了_d后缀区分调试版那这里还需要针对 debug 和 release 分别处理通常用CONFIG(debug, debug|release)和CONFIG(release, debug|release)区分。另一个容易忽视的是opengl模块。Qt 5.12 里 OpenGL 模块被拆分得更细你不仅要QT opengl建议也加上QT openglextensions否则有些头文件会报找不到。3.2 主窗口代码框架让 osgEarth 渲染到 QWidget 里OSG 嵌入 Qt 的经典做法是把osgViewer::Viewer的摄像机图像附着到 QWidget 的窗口句柄上。网上很多教程用的是osgViewer::GraphicsWindowEmbedded配合QWidget::winId()来创建渲染窗口这个思路没错但有几个关键点漏了会直接黑屏或者崩溃。首先QWidget 必须显式设置Qt::WA_NativeWindow属性让它拥有独立的原生窗口句柄。不设置的话winId() 返回的可能是 QWidget 内部合成的窗口OSG 往上面画东西就会出问题。其次你还得处理 Qt 的高DPI缩放否则在 2K 或 4K 屏幕上画面会模糊。最简单的方案是在 main 函数开头调用QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);然后是事件循环问题。OSG 的frame()函数会一直刷新渲染你不能把它放到 GUI 线程里死循环否则窗口拖拽、按钮点击全部卡死。我的做法是用一个QTimer定时调用 viewer 的 frameQTimer timer; QObject::connect(timer, QTimer::timeout, []() { viewer-frame(); }); timer.start(16); // 约 60 FPS如果你需要更高的交互流畅度也可以把渲染放到独立线程但那就涉及线程间同步和 GL 上下文共享的问题复杂度高一截。对大多数 GIS 场景16ms 定时刷新的方案完全够用。3.3 创建 osgEarth 地图节点osgEarth 的加载方式有两种earth 文件XML和代码硬编码。推荐用 earth 文件因为修改地图源、图层配置时不用重新编译。我举个最简单的地形加载例子map nameMyMap typegeocentric options cache typefilesystem pathD:/cache/path /cache /options image layer drivergdal urlD:/data/world.tif/url /image elevation layer drivergdal urlD:/data/srtm.tif/url /elevation /map代码里加载就一行osg::ref_ptrosgEarth::MapNode mapNode osgEarth::MapNode::load(D:/data/map.earth);这里的核心技巧是typegeocentric还是typegeodetic。地心坐标系适合全球视角投影坐标系适合局部区域。如果你的数据是 WGS84 经纬度的影像且要拉得很远看用geocentric可以避免极点附近的畸变如果只是看某一城市的局部用geodetic性能会更好因为少了椭球体变换的数学开销。3.4 配置运行环境 PATH 和 DLL 搜索路径终于到了那个著名的 no qt platform plugin could be initialized 报错环节。这个报错几乎每个 Qt 新手都会遇到场景就是你点了运行程序一闪而过控制台输出这么一行红字。原因其实特别朴素Qt 的程序启动时需要加载 platform 插件qwindows.dll而这个插件位于 Qt 安装目录的plugins/platforms下你的程序找不到它。解决办法有两个层面第一层是开发期临时解决在 Qt Creator 的“项目 → 运行 → 环境”里增加 PATH把D:/dev/Qt/5.12.12/msvc2017_64/plugins加进去。第二层是发布期用windeployqt工具自动把 Qt 相关的 DLL 和插件拷贝到 exe 同级目录命令是windeployqt D:/build/OsgEarthDemo/release/OsgEarthDemo.exe但 windeployqt 只会处理 Qt 自身的依赖OSG 和 osgEarth 的 DLL 它不管。所以你还得手动把 OSG 的bin目录下的所有 DLL 拷到 exe 目录或者再把D:/dev/OSG/OpenSceneGraph/bin和 osgEarth 的 bin 目录也加进 PATH。我这里给个建议开发期全用 PATH 环境变量发布期才拷贝 DLL这样改代码重编时不会因为 DLL 版本覆盖而引入莫名其妙的 bug。3.5 Debug/Release 混合使用的隐藏坑这应该是整套环境里最隐蔽的问题——Qt 的 Debug 和 Release 模式用的库文件名不一样。Qt 在 Debug 模式下链接的是Qt5Cored.dll、Qt5Guid.dllRelease 模式则不带dOSG 库也类似编译时如果你选择了生成调试符号可能得到的是osgd.lib、osgDBd.lib。很多人 .pro 文件里只写了-losg -losgDB结果 Debug 编译时链接器找不到osgd.lib报错LNK1104: cannot open file osgd.lib。解决办法有两个一是写程序时就用CONFIG(debug, debug|release)分别指定不同库名二是干脆只使用 Release 版本的 OSG 库Debug 程序也链接 Release 库。第二种方法不推荐因为 Debug 程序带调试信息而 Release 库做了优化两者混用可能导致调试时看到的值和实际不符、单步跟踪时跳到错误的行号。我推荐的做法是在 .pro 文件里写一段干净的版本判断CONFIG(debug, debug|release) { LIBS -losgd -losgDBd -losgViewerd -losgEarthd -losgEarthUtild } else { LIBS -losg -losgDB -losgViewer -losgEarth -losgEarthUtil }注意 osgEarth 的调试库不一定带了d后缀这个要看你编译时是否开启了CMAKE_DEBUG_POSTFIX。默认情况下 CMake 会加但如果你编译时清掉了这个选项那就只能靠肉眼检查 lib 目录下的文件名来确认了。这东西很烦但一旦配好Debug/Release 切换就是零成本。4. 常见问题与排查技巧实录4.1 启动崩溃与黑屏问题速查这套环境常见的运行问题有好几个我把它们列成一张速查表你按图索骥会快很多现象可能原因解决办法启动提示 no qt platform plugin could be initializedPATH 缺少 Qt plugins 路径在运行环境里把D:/dev/Qt/5.12.12/msvc2017_64/plugins加进 PATH窗口弹出来但全黑没有调用viewer-frame()或未设置线程模型检查 QTimer 定时器是否启动在 viewer 初始化时设置osgViewer::ViewerBase::SingleThreaded加载 .earth 文件没反应控制台无输出缺少 osgdb_earth 插件或 earth 文件路径错误检查 OSG 的 plugin 目录下是否有osgdb_osgearth.dll用绝对路径编译报错无法打开 osgd.libDebug 模式下用了 Release 库名按上文所述在 .pro 里区分 debug/release 库名运行时内存访问冲突崩溃位置在 osgEarth::MapNodeOSG 与 osgEarth 版本不匹配确认 OSG 3.4 对应 osgEarth 2.8不要混用其他大版本这里面最阴间的其实是黑屏问题。很多人以为窗口出来了、程序没崩溃就算成功了结果黑屏上什么都没有这时候第一反应是去看 .earth 文件路径、去看影像数据读取但经常忽略了渲染线程没有跑起来这件事。OSG 的 Viewer 默认是多线程模式在某些 Windows 显卡驱动下多线程渲染嵌入到 QWidget 里会出现无法附着上下文的问题表现就是黑屏。解决方法是显式设置线程模型viewer-setThreadingModel(osgViewer::ViewerBase::SingleThreaded);在开发初期用单线程模式足够而且调试起来特别方便。等到功能稳定了再切换回DrawThreadPerContext或多线程模式提升性能。4.2 osgEarth 插件加载失败的排查思路osgEarth 加载地图时依赖 OSG 的插件机制它的osgdb_osgearth.dll要能被 OSG 找到。这个插件默认在 osgEarth 的bin目录下但 OSG 找插件时不是去 PATH 里找而是去找 OSG 的OSG_LIBRARY_PATH环境变量或者它自己编译时默认设置的插件路径。有几次我明明把 bin 目录加进了 PATH但 osgEarth 文件就是加载不了后来发现是因为osgdb_osgearth.dll依赖的osgEarth.dll在加载时失败了DLL 依赖链断裂。Windows 的 DLL 搜索顺序是先看应用程序目录、再看系统目录、再看 PATH所以我建议最稳的方法是把所有 OSG 和 osgEarth 的 DLL拷贝到 exe 所在目录一劳永逸不依赖 PATH 顺序。判断插件加载是否成功可以在加载 .earth 文件之前设置 OSG 的通知级别osg::setNotifyLevel(osg::INFO);控制台会输出类似这样的信息Info: Registry::loadLibrary(osgdb_osgearth.dll)如果你没看到loadLibrary的成功信息那就说明插件没被找到或加载失败按上面说的方法处理。4.3 性能调优的独门经验当你的程序能跑起来之后就会开始关心性能问题。osgEarth 默认加载高分辨率影像和地形时第一次会很卡这是因为它在做数据调度和缓存。有几个参数值得你调一下第一缓存一定要开。在地球文件里配置 filesystem 缓存后第一次浏览缓存到本地第二次启动速度能提升 5-10 倍。缓存目录放在 SSD 上效果更明显。第二调整 LOD 策略。如果你主要看小范围高精度数据可以把max_range调高让高细节层在更远距离就开始加载反之如果你看全球范围就把min_range调大避免加载过多无谓细节。第三影像和地形并发策略。osgEarth 2.8 的默认调度器是MapNodeProfile在 CPU 核心多的情况下可以把调度器线程数调大options scheduler driverdefault num_threads8/num_threads /scheduler /options不过这里要注意线程数不是越大越好。osgEarth 的数据调理会占用 IO 带宽如果数据在机械硬盘上线程开太多反而会因为磁盘寻道竞争而变慢。我自己实测下来8 核机器配num_threads4是磁盘 IO 和数据解码的甜点值。4.4 粒子效果和扩展方向搜热词里有人提到“osg粒子效果”这是个很好的扩展方向。OSG 自带的粒子系统osgParticle跟 osgEarth 可以无缝集成因为 osgEarth 的 MapNode 本质上是一个 OSG 节点你可以随便往场景里加粒子特效。比如模拟爆炸、喷泉、天气效果都能直接挂在场景图里。我基于这套 Qt osgEarth 工程做过一个简单的粒子需求核心代码就十几行osg::ref_ptrosgParticle::PrecipitationEffect rain new osgParticle::PrecipitationEffect; rain-rain(0.5); mapNode-addChild(rain);PrecipitationEffect是 OSG 自带的最简单的粒子实现雨、雪效果都能出。复杂一点的粒子系统要用osgParticle::ParticleSystem配合ModularProgram和Emitter自己搭但原理是一样的只要能拿到场景根节点的引用粒子就能加到任何位置。对于 GIS 场景来说一个常见的应用是把粒子系统绑定到某个地理坐标上空可以通过osgEarth::GeoTransform节点把局部坐标转换到地理坐标osg::ref_ptrosgEarth::GeoTransform xform new osgEarth::GeoTransform; xform-setPosition(osgEarth::GeoPoint(mapNode-getMapSRS(), 116.39, 39.90, 500)); xform-addChild(rain.get()); mapNode-addChild(xform.get());这样粒子就会出现在北京上空 500 米的位置而且会随着视角远近调整。5. 工程迁移与发布实战要点5.1 换机器后的重新部署难题工程在一台机器上配好了换到另一台机器上可能还会遇到新的问题。最常见的就是路径硬编码。我在 .pro 文件里写的D:/dev/OSG这种路径到另一台机器上可能就变成了E:/libs/OSG你需要通篇替换。一个更好的做法是用 qmake 的环境变量引用OSG_ROOT $$(OSG_ROOT) isEmpty(OSG_ROOT) { OSG_ROOT D:/dev/OSG } INCLUDEPATH $$OSG_ROOT/include LIBS -L$$OSG_ROOT/lib然后在系统环境变量里定义OSG_ROOT这样换机器只需要改一个环境变量不需要动代码。osgEarth 的位置也建议用类似方式处理比如OSGEARTH_ROOT。5.2 发布时的最小依赖集用 windeployqt 打完包之后exe 同级目录会生成一堆 Qt DLL 和 plugins 文件夹。但这只是“最小 Qt 依赖”你还得手动把以下内容拷贝进去OSG 的全部 DLLOpenThreads.dll、osg*.dllosgEarth 的全部 DLLosgEarth.dll、osgEarthUtil.dll、osgEarthQt.dllOSG 插件目录osgPlugins-3.4.0版本号以实际为准osgEarth 插件osgdb_osgearth.dll如果是 Debug 版本所有 Debug DLL 也要带d后缀一个容易忽略的点是C 运行库。如果你用的是 MSVC2017 工具链目标机器上需要有 VC 2015-2022 Redistributable。如果是 Debug 版还需要调试运行时Debug 版 VC Redistributable这个默认不装的话程序会直接报缺少vcruntime140d.dll。发布时建议直接编译 Release 版本省掉这个麻烦。5.3 常见发布后崩溃问题发布版最常遇到的崩溃有两种。第一种是“无法定位程序输入点于动态链接库”这个基本上是因为 exe 启动时加载的某个 DLL 版本不对比如系统里已经装了一个旧版 OSGPATH 优先级把旧版 DLL 给找上了。排查方法是用 [Dependency Walker] 或 [Process Explorer] 查看实际加载的 DLL 路径然后把正确的 DLL 放到 exe 目录下强制优先加载。第二种是“0xc000007b”错误这个错误码的意思是“应用程序无法正常启动”。大概率是 64 位和 32 位二进制混用。比如你编译的是 x64 程序但拷贝进来的某个 DLL 是 32 位的就会触发这个错。检查方法很简单右键 DLL 看属性或者在 VS 开发者命令行里执行dumpbin /headers xxx.dll查看机器类型。、6. 我的最终配置清单与建议为了让你少走弯路我把最终的一套可用的关键配置汇总一下Qt 版本: Qt 5.12.12 MSVC2017 64-bit 编译器: Visual Studio 2017 (MSVC 14.16) OSG 版本: OpenSceneGraph 3.4.1 osgEarth 版本: osgEarth 2.8 CMake: 3.20 第三方依赖: GDAL 2.4, CURL 7.x, SQLite 3.x 编译模式: Release Debug 都编译 OSG_USE_QT: ON 工程构建工具: qmake Qt Creator 4.x这套搭配我前后跑了两个项目一个是地形浏览一个是基于遥感影像的标绘系统运行都挺稳定。最后再分享一个小技巧调试 osgEarth 的 earth 文件配置时不要每次都启动 GUI 程序可以先写一个控制台工具只做加载和打印osg::ref_ptrosgEarth::MapNode node osgEarth::MapNode::load(argv[1]); if (!node.valid()) { std::cerr load failed std::endl; return 1; } std::cout map loaded, layers: node-getMap()-getNumLayers() std::endl;这样排查数据问题比开 GUI 调试快得多。等控制台工具确认 earth 文件没问题再回到 Qt 工程里看渲染交互两边结合效率能高不少。这套环境配置本身不复杂难就难在版本匹配和路径一致性上只要这两点掌握好后面开发就顺了。本文还有配套的精品资源点击获取