2026/10/20 10:10:22

msvc2017+OSG3.63+osgEarth2.10+Qt5.14整合包:快速搭建三维GIS环境

msvc2017+OSG3.63+osgEarth2.10+Qt5.14整合包:快速搭建三维GIS环境 简介本资源面向使用 MSVC2017 工具链进行三维地理可视化开发的 C/Qt 工程师提供 OSG 3.6.3 与 osgEarth 2.10 的完整编译产物及一个可直接运行的 Qt 5.14 示例工程帮助读者跳过繁琐的源码编译与依赖配置环节快速搭建地球场景、加载影像与高程数据。压缩包约 530MB共约 2000 个文件以 hpp 与 h 头文件为主辅以 dll、lib、pdb、ilk 等动态库与调试符号以及 earth、shp、tif、osgb 等地理数据与模型资源另有少量 cpp、bat、pro 工程文件覆盖开发、调试与运行全流程。目前已有 536 人学习下载。借助其中的库文件与示例工程读者可对照博文完成环境搭建理解 Qt 与 OSG 渲染窗口的集成方式并在此基础上扩展地形、矢量与标注图层适合作为 osgEarth 入门与项目原型开发的参考。1. 拿到 msvc2017osg3.63osgearth2.10qt5.14 这套包先搞清楚它能省掉哪三天如果你正在 Windows 上折腾三维地理场景大概率经历过这个循环装 OSG编译 osgEarth配 Qt然后卡在某个链接错误上一卡就是两三天。这套 msvc2017osg3.63osgearth2.10qt5.14 的整合包解决的就是这个「环境搭不起来」的问题。它把编译器、场景图引擎、地理渲染引擎和界面框架的版本组合固定下来省掉从源码逐个编译的环节。适合做三维 GIS 原型、态势可视化、飞行模拟这类桌面端项目的开发者尤其是需要快速验证 osgEarth 图层加载和 Qt 界面嵌合的人。但别急着解压版本组合有它的脾气后面几章会把能跑通的路和容易翻车的地方都拆开讲。2. 版本锁定背后的依赖链为什么是这四个凑一起2.1 msvc2017 与 Qt5.14 的 ABI 匹配逻辑这套组合里msvc2017 不是随便选的。Qt5.14 的预编译二进制包在 Windows 上主要提供 msvc2017 和 msvc2019 两个版本而 osg3.63 的官方二进制包恰好也是用 msvc2017 工具链构建的。如果编译器版本对不上链接阶段会出现LNK2019未解析外部符号报错指向std::_Container_base12之类的内部类型——这是 ABI 不兼容的典型症状。我一般会先确认三件事Qt 的qmake -v输出里编译器标记是不是msvc2017OSG 的osgversion.exe能不能正常打印版本号osgEarth 的osgearth_version.exe是否依赖同一个osg163-osg.dll。三者一致后面才有的谈。注意如果你机器上同时装了多个 MSVC 版本环境变量PATH里cl.exe的搜索顺序会直接影响 CMake 选哪个编译器。用where cl确认一下。2.2 osgEarth2.10 对 OSG3.63 的接口依赖osgEarth2.10 这个版本对 OSG 的依赖不是「大于等于」而是「精确匹配」。它内部调用了 OSG3.63 新增的osg::Shader::setShaderSource重载和osgDB::Options的某些序列化接口换 OSG3.4 或 3.6.5 都会在编译期报错。更隐蔽的是运行时如果osgdb_earth.dll和osgdb_osg.dll来自不同 OSG 构建加载.earth文件时会直接崩溃没有明确报错。常见做法是检查包内bin目录下所有osg*-osg.dll的文件版本号是否一致。在 PowerShell 里可以这样批量看Get-ChildItem C:\path\to\package\bin\osg*-osg.dll | ForEach-Object { $v (Get-Item $_.FullName).VersionInfo.FileVersion Write-Output $($_.Name) - $v }逻辑说明这段脚本遍历 bin 目录下所有 OSG 核心 DLL打印文件版本。如果出现3.6.3和3.6.5混在一起说明包被污染过需要重新解压。参数上FileVersion取的是 PE 文件里的版本资源比文件名可靠。2.3 Qt5.14 与 osgEarth 的嵌入方式选型Qt 嵌 OSG 有两种主流做法一种是QOpenGLWidget里直接调 OSG 的Viewer::frame()另一种是用osgQt库的GraphicsWindowQt。这套包里如果带了osgQt的编译产物优先用后者因为它处理了 Qt 事件循环和 OSG 渲染循环的同步问题。但 osgQt 在 Qt5.14 下有个已知问题QGLWidget被标记为废弃编译时会出一堆警告不影响运行但看着烦。如果包里没有 osgQt那就走QOpenGLWidget路线需要自己写一个继承自QOpenGLWidget和osgViewer::Viewer的类在paintGL()里调frame()。这种方式的坑在于 Qt 的initializeGL()只调一次而 OSG 的realize()需要手动触发。3. 从解压到第一个 osgEarth 窗口环境变量与工程配置3.1 目录结构与必须设置的环境变量假设包解压到D:\Dev\osgqt-package典型目录结构是bin、include、lib、share四个。bin里放 DLL 和可执行文件lib里是.lib导入库include是头文件share里是 osgEarth 的earth插件和 proj 数据。必须设的环境变量有三个变量名值作用OSG_ROOTD:\Dev\osgqt-package供 CMake 查找PATH追加%OSG_ROOT%\bin运行时找 DLLOSG_FILE_PATH%OSG_ROOT%\shareosgEarth 找 proj 和 earth 文件PATH的追加顺序有讲究如果系统里已有其他 OSG 版本必须把新路径放在最前面否则osgviewer.exe加载的是旧 DLL。我习惯在命令行里临时设set PATHD:\Dev\osgqt-package\bin;%PATH%来验证确认没问题再写进系统变量。3.2 CMakeLists.txt 里 find_package 的写法用 CMake 组织工程时find_package(OpenSceneGraph)和find_package(osgEarth)的搜索路径要靠CMAKE_PREFIX_PATH指过去。下面是一个最小可用的CMakeLists.txt片段cmake_minimum_required(VERSION 3.12) project(EarthViewer LANGUAGES CXX) # 指定包根目录让 find_package 能找到 config 文件 set(CMAKE_PREFIX_PATH D:/Dev/osgqt-package ${CMAKE_PREFIX_PATH}) # Qt5 自动处理 moc、uic、rcc set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) find_package(Qt5 COMPONENTS Widgets OpenGL REQUIRED) find_package(OpenSceneGraph REQUIRED COMPONENTS osgViewer osgDB osgGA) find_package(osgEarth REQUIRED COMPONENTS osgEarth osgEarthUtil) add_executable(EarthViewer main.cpp MainWindow.cpp MainWindow.h) target_link_libraries(EarthViewer Qt5::Widgets Qt5::OpenGL ${OSG_LIBRARIES} ${OSGEARTH_LIBRARIES} )逻辑说明CMAKE_PREFIX_PATH指向包根目录后find_package会去lib/cmake下找OpenSceneGraphConfig.cmake和osgEarthConfig.cmake。如果包内没有提供 config 文件就得用FindOSG.cmake模块模式手动设OSG_INCLUDE_DIR和OSG_LIBRARY。参数上COMPONENTS后面列的是实际用到的模块少写会导致链接时缺库。3.3 一个能加载 .earth 文件的最小 Qt 窗口下面这段代码演示在QOpenGLWidget里嵌 osgEarth 的MapNode加载一个本地.earth文件#include QOpenGLWidget #include osgViewer/Viewer #include osgEarth/MapNode #include osgEarth/Map #include osgGA/TrackballManipulator class EarthWidget : public QOpenGLWidget, public osgViewer::Viewer { public: EarthWidget(QWidget* parent nullptr) : QOpenGLWidget(parent) { // 创建地图节点加载 earth 文件 osg::ref_ptrosgEarth::MapNode mapNode osgEarth::MapNode::create(); // 空地图后续可加图层 setSceneData(mapNode); setCameraManipulator(new osgGA::TrackballManipulator); // 设置相机初始位置避免一开始看不到东西 getCamera()-setClearColor(osg::Vec4(0.1f, 0.1f, 0.2f, 1.0f)); } protected: void initializeGL() override { // Qt 的 GL 上下文就绪后再 realize OSG 的图形窗口 if (!isRealized()) { realize(); } } void paintGL() override { frame(); // 驱动 OSG 渲染循环 } void resizeGL(int w, int h) override { getCamera()-setViewport(0, 0, w, h); } };逻辑说明initializeGL()里调realize()是关键否则 OSG 的GraphicsWindow没绑定到 Qt 的 GL 上下文frame()会直接返回。paintGL()里只调frame()不要在里面做场景图修改否则会和 Qt 的渲染线程冲突。参数上setClearColor设的是背景色调试时改成亮色更容易看出渲染是否正常。提示如果窗口出来是黑的先检查OSG_FILE_PATH是否包含share目录再确认.earth文件里的proj路径是不是绝对路径。4. 避坑排查链接错误、DLL 缺失与渲染黑屏4.1 现象LNK2019 未解析符号指向 osgEarth 的某个类原因osgEarth.lib的导入库和实际 DLL 版本不匹配或者 CMake 链接时库的顺序不对。osgEarth 依赖 OSGOSG 的库必须放在 osgEarth 后面。解决在target_link_libraries里把${OSG_LIBRARIES}放在${OSGEARTH_LIBRARIES}前面。如果还报错用dumpbin /exports osgEarth.lib看导出符号里有没有报错的那个函数名。4.2 现象程序启动时报「找不到 osg163-osg.dll」原因PATH里没有包内的bin目录或者被其他版本的 OSG 抢先加载了。解决用where osg163-osg.dll确认实际加载路径。如果是被旧版本抢占把包内bin路径移到PATH最前面。另一个隐蔽情况是 DLL 依赖的msvcp140.dll版本不对用Dependencies工具看依赖树。4.3 现象osgEarth 窗口黑屏但 OSG 自带的 cow 模型能显示原因.earth文件里的图层没加载成功通常是 proj 数据路径不对或网络图层超时。解决在代码里加osgEarth::Registry::instance()-setDefaultCachePolicy前先设osgEarth::Registry::instance()-setProjectionDatabase指向本地proj.db。如果是网络图层检查earth文件里的 URL 是否可达或者临时换成osgEarth::GDALImageLayer加载本地 tif。4.4 现象Qt 界面拖动时卡顿帧率骤降原因QOpenGLWidget默认在 GUI 线程渲染OSG 的frame()如果耗时超过 16ms就会阻塞事件循环。解决把QOpenGLWidget的updateBehavior设为NoPartialUpdate并在paintGL()里用frame()的返回值判断是否需要继续刷新。更彻底的做法是开一个独立线程跑frame()但要注意 GL 上下文不能跨线程共享。4.5 现象CMake 配置时找到的是系统里的旧版 OSG原因CMAKE_PREFIX_PATH没生效或者系统环境变量OSG_DIR指向了别处。解决在CMakeLists.txt开头加set(OSG_DIR D:/Dev/osgqt-package CACHE PATH FORCE)强制覆盖。配置完成后看CMakeCache.txt里OSG_INCLUDE_DIR的值是不是包内路径。5. 进阶用 osgEarth 的 ImageLayer 叠加本地瓦片并验证坐标5.1 加载本地 tif 并设置空间参考osgEarth2.10 里加载本地影像最直接的方式是GDALImageLayer。下面这段代码演示从.earth文件里读图层配置并手动指定SRS#include osgEarth/MapNode #include osgEarth/GDAL #include osgEarth/Map osg::ref_ptrosgEarth::Map map new osgEarth::Map(); // 创建 GDAL 图层指定本地 tif 路径 osg::ref_ptrosgEarth::GDALImageLayer layer new osgEarth::GDALImageLayer(); layer-setURL(D:/data/ortho.tif); // 设置图层坐标系WGS84 经纬度 layer-setProfile(osgEarth::ProfileOptions(global-geodetic)); // 关闭缓存调试时避免读到旧瓦片 layer-setCachePolicy(osgEarth::CachePolicy::NO_CACHE); map-addLayer(layer); osg::ref_ptrosgEarth::MapNode mapNode new osgEarth::MapNode(map); viewer-setSceneData(mapNode);逻辑说明setProfile指定global-geodetic后osgEarth 会把 tif 的坐标映射到 WGS84 经纬度。如果 tif 本身带投影信息GDAL 会自动读取但手动指定可以避免proj.db缺失导致的坐标错乱。NO_CACHE在调试阶段很有用改完 tif 重新运行就能看到效果不用清缓存目录。参数上setURL支持相对路径但相对于OSG_FILE_PATH而不是工作目录。我一般写绝对路径省得排查路径问题。5.2 验证坐标是否正确的两个手段第一个手段是加一个osgEarth::Util::EarthManipulator在窗口标题栏实时显示相机经纬度。第二个手段是放一个已知坐标的PlaceNode看它是否落在影像的正确位置。#include osgEarth/PlaceNode #include osgEarth/AnnotationUtils osg::ref_ptrosgEarth::PlaceNode marker new osgEarth::PlaceNode( osg::Vec3d(116.39, 39.91, 0), // 经纬度注意是 x经度 y纬度 TestPoint, osgEarth::AnnotationUtils::createTextLabelStyle() ); mapNode-addChild(marker);逻辑说明PlaceNode的构造参数里Vec3d的顺序是(经度, 纬度, 高度)和常见的(lat, lon)相反这里翻车过好几次。createTextLabelStyle是 osgEarth 自带的标注样式省得自己写Style。如果标注飘在影像外面先检查 tif 的坐标系和Profile是否一致。5.3 一个我每次都会走的检查流程从那以后我每次拿到新的 osgEarth 包都强制走一遍这个流程先跑osgversion和osgearth_version确认版本再用osgviewer cow.osg确认 OSG 本身能渲染然后跑一个只加载MapNode的空窗口确认 osgEarth 初始化没问题最后才加图层和标注。这样出问题时能快速定位是 OSG 层、osgEarth 层还是数据层的问题。希望帮到你。本文还有配套的精品资源点击获取