简介:面向MSVC2017环境编译的OSG 3.6.3与osgEarth 2.10整合开发包,并附带一个基于Qt5.14的简单示例工程,适用于在Windows下开发三维地球、地理信息可视化应用的工程师与学习者。压缩包内含2000个文件,以11948个hpp与2437个h头文件、538个dll与342个lib链接库、572个exe可执行程序为主体,同时包含111个earth场景描述、44个frag与24个vert着色器文件,可覆盖编译、链接、运行和调试各环节。包体约530.29MB,目录结构清晰,已有532人学习下载。通过该资源可直接获得完整的OSG+osgEarth库、头文件、动态链接库和可执行示例,并参考Qt与OSG窗口集成、视图适配、控件封装等相关源码,省去自行编译OpenSceneGraph与osgEarth的繁琐操作,便于快速搭建环境、验证三维可视化功能或在此基础上二次开发。
1. 拿到 msvc2017+osg3.63+osgearth2.10+qt5.14.7z 这个包,你省下什么
拿到msvc2017+osg3.63+osgearth2.10+qt5.14.7z这个包,等于同时拿到了一整套预编译的 Windows 三维开发环境:MSVC2017 编译器、OpenSceneGraph 3.6.3、osgEarth 2.10 和 Qt 5.14.7。它的价值在于,你不需要再从源码编译 OSG 和 osgEarth 再对接 Qt,解压、配好环境变量、在 CMake 里指定路径,就能直接开始写业务代码。这套东西适合两类人:一类是被第三方库依赖链折腾到头疼的 Qt/OSG 新手;另一类是要快速验证三维地球、地形渲染方案的熟手。下面我把选型逻辑、落地流程和最容易翻车的细节讲清楚,照着做基本能在一个小时内跑出第一个窗口。
2. 版本选型逻辑:为什么这套组合必须原样使用
2.1 决定兼容性的第一要素:MSVC2017 与 MinGW 的 ABI 差异
很多人在 Qt Creator 里新建项目时,默认套件是 MinGW 64-bit,于是直接把解压目录里的库路径填进去,结果一链接就报大量LNK2019、LNK2001无法解析的外部符号,然后开始怀疑库文件损坏。这不是库坏了,是 ABI 不匹配。
C++ 的 ABI 包括符号修饰规则、异常处理方式、RTTI 实现和 new/delete 的内存布局。MSVC 和 MinGW-w64 在这几项上并不通用。这个包里所有 .lib 导入库都是由 MSVC2017 生成的,MinGW 链接器读不懂里面的符号修饰,自然全部落到“未解析”。反过来,你把 MinGW 编出的静态库给 MSVC 工程用,也会得到同样的结果。
所以拿到这类包以后,第一件事是确认工具链统一。在 VS2017 的“开发人员命令提示符”里,用 Qt 自带的 qmake 工具验证当前 Qt 的构建规范:
qmake -query QMAKE_SPEC :: 期望输出 win32-msvc,表示这套 Qt 是 MSVC 构建 cl :: 检查 MSVC 编译器版本,2017 对应 19.1xqmake -query是 Qt 自带的命令行查询工具,QMAKE_SPEC返回win32-msvc说明当前 qmake 来自 MSVC 构建的 Qt。如果你机器上同一环境变量里混着多个 Qt,先确认你 qmake 的是包内Qt-5.14.7\5.14.7\msvc2017_64\bin下的那个。命令行工具最怕“拿 A 版本 qmake,链接 B 版本库”,这属于典型的墨菲定律现场。
| 对比项 | MSVC2017 | MinGW-w64 |
|---|---|---|
| 符号修饰规则 | MSVC 私有格式 | GNU/Itanium 风格 |
| 异常处理 | SEH 扩展 | DWARF/SEH 视架构而定 |
| 与官方 Qt 预编译库兼容性 | 完全匹配 msvc2017_64 | 不匹配 |
| Debug 库命名 | 库名带d后缀 | 通常不带 |
| 混用结果 | 可链接 | 大量无法解析的外部符号 |
2.2 OSG 3.6.3 与 osgEarth 2.10 是配套关系,不是随意组合
osgEarth 2.10 对 OSG 的最低要求是 3.6,而 3.6.3 是 OSG 3.6 分支里的后期补丁版本,修了一堆 64 位下纹理压缩和内存释放问题。把包里的 OSG 换成 3.4 甚至 3.5,osgEarth 2.10 在 CMake 配置阶段就会直接拒绝,报版本过低;把 osgEarth 换成 2.8,又会出现.earth文件里新语法不识别的问题。
这套组合在三维 GIS 老项目里出现概率非常高,原因是 osgEarth 2.10 的 API 相对收敛,很多在 2.8 里需要自己拼的功能,到 2.10 已经封装进MapNode和EarthManipulator。而 OSG 3.6.3 作为当时较成熟的版本,配套的预编译插件也比较齐全,尤其是 GDAL、CURL 这类和地形影像加载相关的第三方插件。
| 组件 | 版本 | 关键原因 |
|---|---|---|
| Visual Studio | 2017 | 所有预编译库的 ABI 基准 |
| Qt | 5.14.7 msvc2017_64 | 官方构建,OpenGL 模块完整 |
| OpenSceneGraph | 3.6.3 | osgEarth 2.10 的最低配套版本 |
| osgEarth | 2.10 | 地形加载 API 成熟,.earth配置通用 |
| 压缩格式 | 7z | 高压缩率,保留目录结构 |
2.3 包内目录结构与 CMake 找包方式的约定
这类 7z 包解压后,根目录下一般会按库名分目录,比如OSG-3.6.3、osgearth-2.10、Qt-5.14.7。每个库内部又是标准的bin、lib、include结构,CMake 配置文件放在lib/cmake下。拿到包先别急着配 PATH,先在 CMake 里用显式路径指过去,能少污染系统环境。
find_package能自动找到库的前提是 CMake 能定位到它的 config 文件。OSG 的 CMake 包名是OpenSceneGraph,osgEarth 的包名是osgEarth,Qt5 的包名是Qt5。只要在命令行里指定一个写满搜索路径的前缀:
cmake .. -G "Visual Studio 15 2017" -A x64 \ -DCMAKE_PREFIX_PATH="D:/env/osg3.63" \ -DOSG_DIR="D:/env/osg3.63/OSG-3.6.3" \ -DOSGEARTH_DIR="D:/env/osg3.63/osgearth-2.10" \ -DQt5_DIR="D:/env/osg3.63/Qt-5.14.7/5.14.7/msvc2017_64/lib/cmake/Qt5"CMAKE_PREFIX_PATH是总入口,CMake 会在它下面找OSG-3.6.3/lib/cmake这类二级目录;OSG_DIR和OSGEARTH_DIR是保险牌,防止某些 config 文件内部用绝对路径写死。Qt5_DIR指向具体的 Qt5 包目录,如果你系统里装过其他 Qt 版本,这一条尤其重要,否则 CMake 缓存里极容易留一个别的版本路径。
3. 解压与首次运行:把这个 7z 包变成可用工程
3.1 用 7z 命令行解压:参数、路径与完整性检查
右键“解压到当前文件夹”不是不能干,但不好控制目标盘和目录层级。用命令行更直观,也方便以后写脚本重建环境。先验包再解压,顺序别反:
7z t msvc2017+osg3.63+osgearth2.10+qt5.14.7z :: 先测试压缩包完整性,CRC 有错别继续解压 7z x msvc2017+osg3.63+osgearth2.10+qt5.14.7z -oD:/env/osg3.63 -yt是 test 模式,只校验不释放,解压前跑一遍能筛掉一半“下载一半文件损坏”的问题。x保留压缩包内的完整目录结构,-o指定输出目录,注意-o和路径之间不能有空格。-y是全部确认,避免解压中途停下来问你是否覆盖。
路径上有一个基本要求:不要解压到带空格或中文的目录。Qt 的插件加载、OSG 的 DLL 搜索、CMake 的 config 解析,在带空格的路径下都可能出诡异问题。如果你实在要放C:\Program Files下,后续踩了坑,第一个怀疑对象就是这里。Linux 下解压 7z 再拷到 Windows 也不是不行,但注意权限位可能丢失,且不要在 SMB 共享目录上直接跑 OSG,锁和缓存会带来很多莫名奇妙的闪退。
3.2 环境变量配置:少而精确,别把 PATH 搞坏
我见过太多人一上来就把包的五个bin目录全塞进 PATH,结果 setx 截断老变量,整台机器别的命令全废了。setx的坑在于它会覆盖原有值,而且超过 1024 字符会被截断。正确做法是只设独立变量,把 PATH 的修改留给运行时按需处理。
setx OSG_ROOT "D:\env\osg3.63\OSG-3.6.3" setx OSGEARTH_ROOT "D:\env\osg3.63\osgearth-2.10" setx QT_ROOT "D:\env\osg3.63\Qt-5.14.7\5.14.7\msvc2017_64"OSG_ROOT和OSGEARTH_ROOT是给 CMake 和运行时找插件用的,QT_ROOT是为了让后续 Qt 命令行工具指向正确版本。setx 写完后,当前终端不会立即生效,必须重新打开一个终端。这不是玄学,是 setx 只写注册表、不更新当前进程环境块导致。
PATH 里到底加不加?运行时如果直接从命令行启动 exe,建议临时加一次:
set PATH=D:/env/osg3.63/OSG-3.6.3/bin;D:/env/osg3.63/osgearth-2.10/bin;D:/env/osg3.63/Qt-5.14.7/5.14.7/msvc2017_64/bin;%PATH%这条只对当前终端有效,不会污染系统。开发时不依赖 PATH,而是靠 CMake 变量把库位置传进去,这样换机器、换路径都只需改一处。
3.3 跑通最小可运行程序:用 CMake 和 MSVC2017 链接 OSG 与 Qt
这个阶段的目标不是做界面,是证明“这条库链能编译、能链接、能运行”。写一个最小工程,只生成一个立方体窗口。
CMakeLists.txt:
cmake_minimum_required(VERSION 3.15) project(osgMin) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt5 REQUIRED COMPONENTS Core) find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgGA osgViewer osgUtil) add_executable(osgMin main.cpp) target_link_libraries(osgMin PRIVATE Qt5::Core ${OPENSCENEGRAPH_LIBRARIES})main.cpp:
#include <QString> #include <QDebug> #include <osgViewer/Viewer> #include <osg/ShapeDrawable> #include <osg/Geode> int main(int argc, char** argv) { // 这行只是为了确认 Qt5Core.dll 参与运行时链接 QString title = QString::fromUtf8("osg+qt lib chain ok"); qDebug() << title; // 创建一个立方体作为场景数据,不依赖外部模型文件 osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(new osg::ShapeDrawable( new osg::Box(osg::Vec3(0,0,0), 1.0f))); osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer; viewer->setSceneData(geode.get()); viewer->setUpViewInWindow(100, 100, 800, 600); viewer->run(); return 0; }QString和qDebug不参与渲染,它们在这里只为确保 Qt5Core 的导入库和 DLL 真的被链接进产物。osg::Box构造参数是中心点和边长,ShapeDrawable负责把几何体转成可绘制的图元。setUpViewInWindow让 Viewer 自己创建一个独立窗口,这是最简验证路径,不涉及 Qt 窗口嵌入。viewer->run()进入渲染循环,看到旋转的立方体说明整条链是通的。
编译命令:
cmake .. -G "Visual Studio 15 2017" -A x64 -DCMAKE_PREFIX_PATH="D:/env/osg3.63" cmake --build . --config ReleaseVS 15 2017对应 Visual Studio 2017,-A x64指定 64 位架构,这是必须的,包内库都是 x64。CMAKE_PREFIX_PATH只给根目录,CMake 会自动向下找。编译完成后,产生的 exe 和包内bin目录放在一起运行最省心,否则就把OSG_ROOT\bin加进临时 PATH。
4. osgEarth 地形场景与 Qt 窗口嵌入:加载参数和插件路径
4.1 用本地影像和高程数据生成可加载的 .earth 配置
osgEarth 的入口是.earth文件,它用 XML 描述地图的投影、影像源和高程源。最简单的本地配置长这样:
<map version="2"> <options> <profile>global-geodetic</profile> </options> <image driver="gdal"> <url>../data/imagery.tif</url> </image> <elevation driver="gdal"> <url>../data/srtm.tif</url> </elevation> </map>profile指定地理坐标系,global-geodetic是经纬度全球剖面,适合直接加载带地理坐标的影像和高程。driver="gdal"告诉 osgEarth 用 GDAL 插件读文件,这套预编译包里一般会带上 osgdb_gdal 插件,但插件本身还要依赖 libgdal 的 DLL,缺一个都会在运行时静默失败。url这里用的是相对路径,注意它是相对于当前工作目录,不是相对于.earth文件所在目录。
验证这步最简单的方式是找包内 bin 目录里带 osgearth 字样的示例程序,随便挑一个能加载地图文件的,先看一眼运行参数。常见写法是:
osgearth_simple --window 800 600 earth.earth如果示例程序名不是这个,翻一下 bin 目录里实际存在的 exe,这我不替包作者打包票。关键是确认 exe 的工作目录里能找到earth.earth,且../data/下的两个 tif 路径真实存在,否则你会得到一个渲染完但一片灰白的地球,日志里只有一行不起眼的Could not open url。
4.2 OSG 插件目录与 Qt 插件目录:两套 DLL 寻址规则
OSG 运行时要找osgPlugins-3.6.3目录下的插件 DLL,读取earth文件时用的 GDAL 驱动就藏在这里。Qt 运行时要找plugins\platforms\qwindows.dll,缺失时程序往往在窗口出现前就退出,事件日志里只留下一条找不到 qwindows 的记录。两套寻址逻辑互不相干。
setx OSG_LIBRARY_PATH "D:\env\osg3.63\OSG-3.6.3\bin\osgPlugins-3.6.3" setx QT_QPA_PLATFORM_PLUGIN_PATH "D:\env\osg3.63\Qt-5.14.7\5.14.7\msvc2017_64\plugins"OSG_LIBRARY_PATH是给 OSG 找插件用的,设置后它会在 PATH 之前优先搜索这里。QT_QPA_PLATFORM_PLUGIN_PATH是 Qt 的 Qt Platform Abstraction 插件路径,设置后 Qt 在 Windows 上能定位到qwindows.dll。这两个变量都是运行时用,编译期不需要。
提示:如果你把 exe 放在了某个单独目录,且这个目录里没放osgPlugins-3.6.3,运行时会提示找不到插件。最简单的交付方式是把 exe 放进OSG_ROOT\bin下,让插件同目录加载,后续发布程序时再用 windeployqt 和手动拷贝补依赖。
4.3 用 QOpenGLWidget 把 osgEarth 场景嵌进 Qt 窗口:初始化顺序与 QSurfaceFormat 参数
如果你不想用 osgQt 模块(部分预编译包默认不编这个模块),最通用的方式是自己继承 QOpenGLWidget。要注意:QOpenGLWidget 的 OpenGL 上下文并不是随时可用的,必须在initializeGL回调里创建 viewer 和场景,在paintGL里驱动渲染。
#include <QOpenGLWidget> #include <QSurfaceFormat> #include <osgViewer/Viewer> #include <osgDB/ReadFile> #include <osgEarth/EarthManipulator> class EarthView : public QOpenGLWidget { public: EarthView(QWidget* parent = nullptr) : QOpenGLWidget(parent) {} protected: void initializeGL() override { // OpenGL context 在这里才就绪,不要在构造函数里创建 viewer osg::ref_ptr<osg::Node> node = osgDB::readRefFile("earth.earth"); if (!node) { qWarning("earth.earth 加载失败,请检查当前工作目录"); return; } _viewer = new osgViewer::Viewer; _viewer->setSceneData(node.get()); _viewer->setCameraManipulator(new osgEarth::EarthManipulator); // 生产环境需要把 viewer 的 GraphicsWindow 与当前 context 绑定, // 优先看包内 osgQt 是否提供 osgQOpenGLWidget,那个封装更完整。 } void paintGL() override { if (_viewer.valid()) _viewer->frame(); } private: osg::ref_ptr<osgViewer::Viewer> _viewer; };在 main 里,QSurfaceFormat必须在创建QApplication之前设置,否则不会作用于后续所有窗口:
#include <QApplication> int main(int argc, char** argv) { QSurfaceFormat fmt; fmt.setDepthBufferSize(24); fmt.setSamples(4); QSurfaceFormat::setDefaultFormat(fmt); QApplication app(argc, argv); EarthView w; w.resize(1024, 768); w.show(); return app.exec(); }setDepthBufferSize(24)是关键参数。osgEarth 地形是大量多边形叠加,没有深度缓冲会疯狂闪烁,也就是常说的 z-fighting。setSamples(4)是多重采样抗锯齿,4 倍足够,集成显卡上开到 8 可能拖慢帧率。很多初次接触的人遇到黑屏,第一反应是“代码写错了”,其实先检查这两项:context 是否在 initializeGL 之前被创建、深度缓冲有没有开。这种排查思路在图形开发里永远不会过时,黑匣子一旦打开,80% 的问题都集中在初始化顺序上。
5. 避坑与排查:这套组合包使用中常见的 5 条血泪经验
5.1 CMake 报依赖路径指向其他 Qt 版本
现象:CMake 配置时报错,信息里面有一长串dependent '..\..\..\..\qt\5.15.2\msvc2019_64\include\...或者类似的其他 Qt 安装路径。
原因:系统里装过多个 Qt,CMake 缓存里Qt5_DIR指向了 5.15.2 的 msvc2019_64,或者 PATH 里混入了新版本 Qt 的 bin,导致 moc、uic 等命令行工具被旧/新版本抢占。
解决:删掉 build 目录里的 CMakeCache.txt,重新配置时显式传入-DQt5_DIR="D:/env/osg3.63/Qt-5.14.7/5.14.7/msvc2017_64/lib/cmake/Qt5"。同时打开系统环境变量,把 PATH 里其他 Qt 版本路径暂时移走。这种缓存问题往往最像玄学,清掉 build 目录重来一次就好。
5.2 程序启动即崩溃:Debug 库与 Release 库串台
现象:程序编译通过,运行后立刻闪退,事件查看器里异常模块是 Qt5Cored.dll 或 osgViewerd.dll。
原因:CMake 用了 Release 配置,链接器却因为隐式依赖把 Debug 版本的 DLL 拉进来了。OSG 和 osgEarth 的 Debug 库在文件名中有d后缀,Qt 的 Debug DLL 也有d后缀。一旦 Python 脚本、自定义工具链或 CMake 变量把 Debug 和 Release 混在一起,程序在内存管理上直接冲突。
解决:严格区分构建类型。命令行编译时--config Release,Qt Creator 里也要保证套件与构建目录一致。检查程序加载的 DLL 列表,确认没有带d后缀的库混入。如果已经混了,清理 cmake 缓存,重新配一次。
5.3 场景空白或提示找不到 osgPlugins-3.6.3
现象:程序不崩溃,但场景是空的,打开日志看到Warning: could not find plugin to read objects from file。
原因:OSG 的插件目录没有暴露给运行时。osgDB 加载模型和 osgEarth 加载 GDAL 数据都需要osgPlugins-3.6.3目录存在,而且该目录下要有对应的 DLL。
解决:确认OSG_LIBRARY_PATH已设置;用 VS2017 开发者命令行工具查看插件的依赖链:
dumpbin /dependents D:/env/osg3.63/OSG-3.6.3/bin/osgdb_gdal.dll检查输出列表里每个 DLL 是否都能在 PATH 或系统目录中找到,尤其是libgdal相关 DLL。缺哪个就补哪个,别只看插件文件存在就认为万事大吉。
5.4 7z 解压后 DLL 被杀毒软件隔离
现象:解压过程中提示“访问被拒绝”,或者解压完之后某个 DLL 不见了,运行时报缺失模块。
原因:预编译库中的 DLL 被 Windows Defender 或企业杀软当作潜在威胁处理,尤其是 GDAL 这种带大量第三方依赖的库,容易误报。
解决:先执行7z t验证压缩包本身没有 CRC 错误,再对解压目录添加杀软排除项。不要图省事直接关闭杀软,把目录加白名单即可。下载文件时顺手记录一下 SHA-256,校验一致后再解压,能省掉很多重复下载的时间。
5.5 Qt Creator 用 MinGW 套件编译报 LNK2019
现象:在 Qt Creator 里打开工程,默认选中 MinGW 64-bit 套件,编译时大量LNK2019未解析的外部符号,指向 osgViewer、osgEarth 等库。
原因:包内所有库都是 MSVC2017 编译的,MinGW 的导入库格式和符号修饰不一致。这不是库路径写错,是工具链根本不匹配。
解决:在 Qt Creator 里创建“桌面 Qt 5.14.7 MSVC2017 64bit”套件,编译器指向 VS2017 的cl.exe,或者干脆不用 Qt Creator 做构建,直接用 Visual Studio 2017 打开 CMake 工程。记住:包名里的msvc2017不是装饰,是这套环境的地基。
6. 进阶验证:用 dumpbin 检查 DLL 依赖,把新库塞进这套环境
环境用顺手以后,很多人会想往这套包里加新库,比如第三方点云库或自定义插件。在此之前,先学会验证依赖关系。dumpbin是 VS 自带的命令行工具,在“VS2017 开发人员命令提示符”里直接可用:
dumpbin /dependents D:/env/osg3.63/OSG-3.6.3/bin/osgDB.dll输出里会出现它依赖的 DLL 列表。你要看的是每一项是否都能在系统目录或包内bin目录找到,尤其注意有没有指向别的 Qt 版本。如果某个依赖显示为绝对路径,指向C:\Qt\5.15.2\msvc2019_64\bin\Qt5Core.dll,说明当时编库的机器带了另一个 Qt,这就要格外小心。
新增第三方库时,我一般习惯遵循三步:先用 Visual Studio 2017 打开库源码,把运行库设为“多线程 DLL”(/MD),编译 Release;再把 include、lib、DLL 分别放进包内对应目录;最后用 dumpbin 验证新 DLL 的依赖都落在本包范围内。不要用/MT静态运行时,否则和 OSG、Qt 的/MD导入库在内存分配上会各管各的,运行大了必出问题。
这些年调过不少类似的预编译环境,最深的体会是:环境问题 80% 出在库 ABI 不一致,剩下 20% 是没认真看依赖日志。花十分钟用 dumpbin 把依赖关系理清,比反复删除重装、到处查资料省心得多。希望这些经验帮到你。
本文还有配套的精品资源,点击获取