
简介这是一份面向Windows桌面三维开发的预编译资源包定位是MSVC2017环境下整合OSG 3.6.3、osgEarth 2.10与Qt 5.14的开发依赖集合适合需要快速搭建三维地球/GIS场景而不想从源码编译的C工程师。压缩包为7z格式约530MB包含约2000个文件其中hpp、h头文件提供API声明dll与lib对应运行/链接库exe、pdb、ilk等包含示例程序与调试信息earth、osg、osgb、shp、tif等文件覆盖场景、地形、影像与矢量数据便于直接测试加载效果。资源还附有一个简单Qt工程预览可见GraphicsWindowQt、QGraphicsViewAdapter、mainwindow、QWidgetImage等关键代码有助于理解osgEarth与QWidget窗口的嵌入、适配及渲染流程。已有532人学习/下载适合刚接触OSG/osgEarth在Qt中集成的开发者作为参考起点。1. msvc2017osg3.63osgearth2.10qt5.14.7这套7z组合到底值不值得解压msvc2017osg3.63osgearth2.10qt5.14.7 这串名字放到一起就是一套让许多三维GIS从业者绕不开的Win平台地基。解开这个7z你能得到MSVC编译器对应的C运行库对齐、OpenSceneGraph 3.6.3渲染框架、osgEarth 2.10地形引擎以及Qt 5.14.7界面库四者拼起来恰好覆盖“桌面工具 三维数字地球”的完整链路。它最大的价值不是让画面多炫而是把第三方库之间最折磨人的二进制兼容问题提前解决省掉从源码编GDAL、GEOS、curl那两周工期。适合的人群很具体要在Windows上做地形可视化、态势标绘、仿真训练系统又不想一上来就被依赖折磨的C开发者。这套组合不算最新但足够稳很多项目直接拿它当打包基线。2. 版本搭配逻辑为什么是msvc2017、osg3.6.3、osgearth2.10和qt5.14.72.1 MSVC编译器与MinGW的ABI差异先说“msvc2017”这个前缀。它指的是Visual Studio 2017的v141工具集Qt官方Windows发行版里专门有“msvc2017_64”目录就是这个工具集的产物。很多人拿到包之后第一件事是问“我能不能用MinGW去编工程”答案是不能而且原因不是命令行长得不一样。MSVC和MinGW的区别本质是C ABI层完全不互通符号修饰规则不同异常处理模型不同标准库实现也不同MSVC用微软自己的STLMinGW用libstdc。一个用MinGW编出来的静态库或DLL给MSVC工程链接最典型的报错是“无法解析的外部符号 _imp”或者干脆在程序启动时崩在C运行时初始化上。这些第三方库的预编译包既然标了msvc2017头文件里的声明、lib的导入符号和dll内部的导出表全部按MSVC规则生成。你的工程就必须选MSVC编译器否则后面跑Qt的qwindows平台插件会先翻车。提示Qt Creator里编译器要选MSVC 2017 64bit或者用CMake生成器“Visual Studio 15 2017”不要选MinGW那一栏。另外注意运行库分布MSVC编译的程序需要机器上有Visual C Redistributable多数Windows Server和个人机上已经装过但干净环境会弹“缺少VCRUNTIME140.dll”。安装最新版VC运行库是这套7z能跑起来的隐性前提。2.2 OSG 3.6.3与osgEarth 2.10的版本钳位OSG 3.6.3放今天已经不算年轻但它依然是开源场景图库里被验证最多的一代。osgEarth 2.10的源码要求OSG版本至少3.4因此OSG 3.6.3和osgEarth 2.10是一对老搭档不是随便凑的相识能“3.4以上”细看就锁定在3.6这条线。版本锁真正的意义在插件目录。osgDB运行时去加载osgPlugins-3.6.3目录下的插件如果你的PATH里混入另一个版本的OSG插件目录通常表现是earth文件加载时不报错但新建的图层节点为空最后屏幕黑乎乎的。osgEarth也有自己的插件集合osgEarth_xxx.dll它放在osgearth2.10的bin里并且依赖OSG的底层DLL。如果OSG和osgEarth不是同一套包DLL之间会出现函数签名对不上程序启动直接崩溃而不是提示哪个库缺失。组件版本在包里的作用关键运行目录MSVC2017v141工具集编译工具链与ABI基线需要VS2017或兼容工具集OpenSceneGraph3.6.3场景图渲染osg3.6.3/bin osgPlugins-3.6.3osgEarth2.10地形调度、影像/高程层osgearth2.10/binQt5.14.7界面、事件循环qt5.14.7/msvc2017_64/bin2.3 版本取舍这套组合的边界在哪从时间线看Qt 5.14.7是5.14系列最后一个补丁版本之后Qt官方对msvc2017的官方构建也停留在5.14这条线上。很多帖子里搜“qt 5.14.2下载”其实指的就是这个系列拿到的是某个补丁号这套7z直接固定在5.14.7省去自己再对齐补丁。对工业项目来说这是很大的优点组件的行为完全确定不会因为某个小版本升级让OSG的osgQt模块重新编译。使用边界也要说清楚这套组合不建议拿来追新功能。osgEarth 2.10的路由、遮挡剔除、gltf支持都不如2.x后期和3.x丰富Qt 5.14也没有QML对高性能场景的优化。它适合的场景是“要稳定跑三维GIS业务而不是研究渲染新特性”。把预期放准后面配置起来才不会反复改依赖。3. 解压与配置把7z变成CMake能发现的三方库3.1 目录规划先定路径再谈编译解压7z之前先想好放哪。这个包解出来一般有几十个目录上百个DLL如果路径带中文、带空格最直接受害者是CMake和osgEarth的GDAL驱动运行时各种找不到文件。我一般放D:/gisdev/下整理成下面结构D:/gisdev/ osg3.6.3/ bin/ include/ lib/ osgearth2.10/ bin/ include/ lib/ qt5.14.7/ msvc2017_64/ bin/ lib/ plugins/ data/ map.earth dem.tif cache/解压后先做三件事。第一确认每个库根目录下有bin、include、lib三层缺一不可缺include你没法写代码缺lib没法链接缺bin运行找不到dll。第二确认Qt的msvc2017_64目录里有plugins/platforms/qwindows.dll这个文件缺失会直接导致程序起不来。第三检查OSG的bin目录下有没有osgPlugins-3.6.3目录这是插件集合没有它连图片纹理都加载不了。3.2 环境变量配置PATH顺序是门玄学环境变量最核心的是PATH。推荐的顺序是把Qt的bin放最前因为Qt的DLL依赖VC运行库但也可能被系统目录里的旧版本抢先然后放OSG的bin最后放osgearth的bin。你可能会想“系统里的DLL怎么会有影响”实际经验是系统PATH里若存在另一个版本的Qt5Core.dll程序加载到的DLL可能是系统那个最终qt.conf都救不回来表现得不稳定又难查。环境变量推荐值作用PATHD:/gisdev/qt5.14.7/msvc2017_64/bin; D:/gisdev/qt5.14.7/msvc2017_64/plugins/platforms; D:/gisdev/osg3.6.3/bin; D:/gisdev/osgearth2.10/bin运行期DLL搜索路径OSG_FILE_PATHD:/gisdev/data让OSG找示例模型和纹理OSGEARTH_DATA_PATHD:/gisdev/data让osgEarth找earth文件和栅格数据注意Qt Creator的运行环境和系统环境是分开的。你在“项目 → 构建环境”里加了上述变量但直接在命令行运行exe时系统PATH不会带Qt插件目录两条路都要配好。OSG_FILE_PATH和OSGEARTH_DATA_PATH配了之后earth里的相对路径会优先到这两个目录找数据尤其在后续换机器、换数据目录时能少改很多配置。3.3 CMake工程入口最少配置跑通find_package配置完环境接下来是让CMake能找到这四个库。新建工程目录D:/gisdev/proj/osgdemo放一个CMakeLists.txtcmake_minimum_required(VERSION 3.16) project(OsgEarthQtDemo) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) # 三个库的根目录统一交给CMAKE_PREFIX_PATH set(CMAKE_PREFIX_PATH D:/gisdev/qt5.14.7/msvc2017_64 D:/gisdev/osg3.6.3 D:/gisdev/osgearth2.10 ) # 预编译包通常使用动态运行库这里显式对齐 set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) find_package(Qt5 REQUIRED COMPONENTS Widgets) find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgGA osgUtil osgViewer) find_package(osgEarth REQUIRED) add_executable(${PROJECT_NAME} main.cpp) target_link_libraries(${PROJECT_NAME} Qt5::Widgets ${OpenSceneGraph_LIBRARIES} ${OSGEARTH_LIBRARIES} )这段CMake里要重点理解两个地方。CMAKE_PREFIX_PATH让find_package在指定目录下找Qt5Config.cmake、OSGConfig.cmake、osgEarthConfig.cmake这三个配置文件分别在各自库的lib/cmake子目录只要根目录给对就行。CMAKE_MSVC_RUNTIME_LIBRARY设为MultiThreadedDLL表示编译出来的exe链接动态版VC运行库/MD这和预编译后缀包的Release库一致可以避免后面运行时崩溃。另外add_executable里我没加WIN32标志这样会保留控制台窗口排错阶段能看到osgDB打印的日志。等项目稳定想改成纯窗口程序再加上WIN32即可。4. 第一个可运行程序在Qt窗口里渲染一张真实地形4.1 完整工程main.cpp map.earth配置好CMake以后写一个能跑的最小程序。先准备一张earth文件它是osgEarth的配置入口描述有哪些图层和缓存策略。放在D:/gisdev/data/map.earthmap options profilegeographic/profile cache typefilesystem pathD:/gisdev/cache/path /cache /options image drivergdal urlD:/gisdev/data/land_color.tif/url /image elevation drivergdal urlD:/gisdev/data/dem.tif/url /elevation /map逻辑说明image节点放影像数据决定你看到的地表颜色elevation节点放高程数据决定地形起伏。driver“gdal”表示用GDAL读GeoTIFF这对文件格式兼容性最好tif、img、vrt都能读。cache里开了文件缓存第一次加载会把切片落到本地第二次启动快非常多这个缓存对项目是“后悔药”尤其处理超大影像时能省掉重复IO。profile“geographic”表示数据是经纬度投影如果你的数据是投影坐标这里要改成对应投影定义否则渲染出来的地形位置完全对不上。然后是main.cpp把osgEarth挂到Qt窗口里#include QApplication #include QMainWindow #include osgViewer/Viewer #include osgEarth/MapNode #include osgEarth/MapNodeHelper #include osgEarthQt/ViewerWidget class MainWindow : public QMainWindow { public: MainWindow(osgEarth::MapNode* mapNode) { // osgViewer是OSG的渲染入口把MapNode作为场景根节点 _viewer new osgViewer::Viewer; _viewer-setSceneData(mapNode); // osgEarthQt的ViewerWidget把渲染循环接到Qt事件循环里 osgEarth::Qt::ViewerWidget* widget new osgEarth::Qt::ViewerWidget(_viewer); setCentralWidget(widget); } private: osg::ref_ptrosgViewer::Viewer _viewer; }; int main(int argc, char** argv) { QApplication app(argc, argv); // 加载earth配置文件0L表示使用默认参数 osg::ref_ptrosg::Node node osgEarth::MapNodeHelper().load(D:/gisdev/data/map.earth, 0L, 0L, 0L); if (!node.valid()) return 1; // 失败时先看控制台日志 osgEarth::MapNode* mapNode osgEarth::MapNode::findMapNode(node); if (!mapNode) return 1; MainWindow win(mapNode); win.resize(1280, 800); win.show(); return app.exec(); }代码逻辑并不复杂但三个细节值得说明。MapNodeHelper().load返回的不一定是MapNode本身可能是场景图里包了一层Group所以要再用findMapNode向上遍历找出真正的MapNode节点。ViewerWidget来自osgEarthQt库它内部把OSG的渲染帧绑定到Qt的刷新机制上不需要你手动调用frame()。viewer的生命周期用osg::ref_ptr管理MapNode由场景图持有这地方如果memory管理出错程序退出时容易在析构里崩溃。4.2 渲染循环与关键交互参数这个程序跑起来后你看到的是一个可以鼠标拖拽的数字地球。拖拽和缩放来自osgEarth自带的EarthManipulator相机操控器但需要手动挂到viewer上#include osgEarthUtil/EarthManipulator osgEarth::Util::EarthManipulator* manip new osgEarth::Util::EarthManipulator; manip-setViewpoint(osgEarth::Viewpoint( start, 116.39, 39.90, 0.0, 0.0, -42.0, 3000.0)); _viewer-setCameraManipulator(manip);Viewpoint参数从左到右依次是视点名、经度、纬度、高程、朝向、俯仰角、到目标的距离。俯仰-42度相当于斜着看地面适合观察地形起伏如果设0度会变成俯视看着像平面图。EarthManipulator默认支持鼠标左键旋转、右键缩放、中键平移这些行为不用自己写。另外两个参数值得调。一是日志级别排错时在系统环境变量里设OSGEARTH_LOG_LEVEL2能看每层切片加载情况放到生产环境再关掉避免日志拖慢帧率。二是最大帧率OSG的Viewer有setRunMaxFrameRate接口限制到60帧能有效降低Qt界面卡的感受如果不设它会在高性能显卡上跑满几百帧白白占GPU。4.3 与Qt界面设计集成的正确姿势很多人做qt界面设计时习惯在QWidget的paintEvent里直接画东西遇到OSG就不该这么干。ViewerWidget自己持有OpenGL上下文任何外部对GL状态的修改都可能让它渲染错乱。如果你要在三维场景上叠加标绘、文字、图例常见做法是上下两层QWidget底部放ViewerWidget负责渲染OSG顶部放一个透明QWidget用QPainter画矢量线和标注。透明层要设置setAttribute(Qt::WA_TransparentForMouseEvents)否则鼠标事件会被透明层吃掉地球就拖不动了。这个思路在qt绘图里属于“双窗口叠层”做态势系统时几乎都会用到。5. 避坑记录从解压到首帧渲染的高频翻车点5.1 qt.qpa.plugin: could not find the Qt platform plugin “windows”这个报错几乎人人都会遇到一次。现象是程序启动后立刻弹错或静默退出控制台打印出“qt.qpa.plugin: could not find the Qt platform plugin “windows” in “””这行字。原因不是你的代码问题而是Qt运行时找不到platforms/qwindows.dll。Qt的QPA插件不会自动搜索Qt安装目录它靠QT_QPA_PLATFORM_PLUGIN_PATH或PATH里的plugin路径定位在Qt Creator里能跑双击exe就崩多半是运行环境没带上插件目录。解决分两步把qt5.14.7/msvc2017_64/plugins/platforms这个目录加进PATH或者更稳妥在exe旁边建platforms文件夹把qwindows.dll拷进去。后者适合发布绿色包。排查时先确认qwindows.dll存在再确认路径是64位Qt的插件目录因为混入32位插件会报另一种错误。5.2 找不到osgEarth.dll或libosgQt库的dll现象程序编译通过运行时报“无法启动因为计算机缺少osgEarth.dll”或类似弹窗。原因osgearth2.10/bin以及osg3.6.3/bin没有进入PATHWindows按PATH顺序找不到DLL但又不会帮你自动追加。解决把这两个bin目录加入PATH后重启Qt Creator注意是重启而不是重新构建因为进程启动时已经缓存了环境变量。另外确认位数如果编译的是x64工程所有DLL都必须是x64哪怕缺一个32位DLL跳进来也会出现异常行为而不是明确报错。5.3 加载earth文件后黑屏或只有背景色这个坑通常不是崩溃而是黑屏osgEarth不像普通程序那样缺文件会弹窗它把错误都写进日志。原因通常是三类earth里的tif路径不存在、GDAL驱动本身没被加载、缓存目录写入失败。当drivergdal但osgEarth_gdal.dll不在osgearth2.10的bin目录里osgEarth不会直接炸而是静默跳过图层。解决方法是先把OSGEARTH_LOG_LEVEL设成2看清楚日志里有没有“failed to open”或“could not locate plugin”的字样再对照路径修正。提示黑屏排查顺序是“日志 → 数据路径 → 插件存在 → cache权限”不要一开始就怀疑着色器代码。5.4 Qt事件与OSG事件器冲突鼠标拖不动地球现象窗口能显示但鼠标点击没有任何反应或者只能拖动窗口本身。原因ViewerWidget的鼠标事件被Qt高层截获没有传进OSG的事件器或者你在透明覆盖层上开了WA_TransparentForMouseEvents但还留了Qt::StrongFocus事件被吃掉一半。解决办法是给透明覆盖层只保留绘制能力交互完全交给ViewerWidget如果你在Viewer上挂了自己的GUIEventHandler记得在handle里返回false而不是true否则事件链断掉。这类问题最像玄学排错最有效的方法是把事件过滤器全部去掉确认原生的EarthManipulator能用再一点点加自己的逻辑。5.5 debug与release混用导致的随机崩溃现象非常恼人程序在Debug模式下一会儿崩、一会儿正常Release模式却稳定。原因在于这个7z包通常是Release版预编译osg的库是osgDB.dllQt的是Qt5Widgets.dll你的工程如果用Debug模式链接到的Qt5Widgetsd.dll和osgDBd.dll并不存在CMake可能因为名字混乱、找错库最终混搭了CRT的堆管理逻辑。Debug版Qt和Release版OSG在内存分配器上的行为不同表现在变量释放时交叉free随机崩溃。解决CMake配置阶段选择Release或手动指定import lib为release版本。如果想用Debug模式做断点调试最稳妥的做法是找同一批源码把OSG和osgEarth完整编出Debug包否则Debug下出现的崩溃往往查不出原因白耗大半天。5.6 MSVC运行时库(MT/MTd与MD/MDd)不匹配这条是很多自认老手也会翻车的点。现象一切配置都对一运行就报访问违规0xC0000005或弹“应用程序无法正常启动0xc000007b”。原因预编译的osgEarth默认使用动态运行库MD编译即链接vcruntime140.dll你的工程CMake里如果设置了CMAKE_MSVC_RUNTIME_LIBRARY为MultiThreadedMTexe里会用静态运行库跨库传递STL对象或new/delete时两边运行时不一致栈和堆就错乱了。解决整条工具链统一到“Release-DLL”。CMake里set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)并且不要在项目里额外定义_ITERATOR_DEBUG_LEVEL。血泪经验是这个错不是找不到库而是“找得到但语义不一致”所以检查清单里要把运行库这一项和库路径并列写进去。6. 进阶验证用鼠标点击拾取经纬度确认环境不是“能显示而已”6.1 把鼠标点击变成经纬度查询能显示地形只是第一步真正确认这套环境靠谱的做法是跑一个拾取交互点击屏幕任意位置输出对应的经纬度。如果这个功能正确说明坐标转换、相机矩阵、MapNode的SRS定义都通了。实现一个事件处理器#include osgGA/GUIEventHandler #include osgUtil/LineSegmentIntersector #include osgUtil/IntersectionVisitor #include osgEarth/GeoPoint class PickHandler : public osgGA::GUIEventHandler { public: PickHandler(osgEarth::MapNode* mapNode) : _mapNode(mapNode) {} bool handle(const osgGA::GUIEventAdapter ea, osgGA::GUIActionAdapter aa) { if (ea.getEventType() ! osgGA::GUIEventAdapter::PUSH) return false; // 从投影坐标构造一条屏幕射线 osgUtil::LineSegmentIntersector* picker new osgUtil::LineSegmentIntersector( osgUtil::Intersector::PROJECTION, ea.getXnormalized(), ea.getYnormalized()); osgUtil::IntersectionVisitor visitor(picker); osgViewer::View* view dynamic_castosgViewer::View*(aa); view-getCamera()-accept(visitor); if (!picker-containsIntersections()) return false; osg::Vec3d world picker-getFirstIntersection().getWorldIntersectPoint(); // world是地心坐标系转换成WGS84经纬度 osgEarth::GeoPoint point(_mapNode-getMapSRS(), world); osgEarth::SpatialReference* wgs84 osgEarth::SpatialReference::create(wgs84); point.transform(wgs84); osg::notify(osg::NOTICE) lat: point.y() lon: point.x() std::endl; return false; } osg::ref_ptrosgEarth::MapNode _mapNode; };这段代码的关键是把屏幕像素转成投影空间射线再用osgUtil的LineSegmentIntersector求交。拾取到的world点是地心坐标ECEF必须通过GeoPoint转到地图的SRS再transform成wgs84最后输出的才是人能读懂的经纬度。如果你直接打印world.x/world.y得到的会是几百万米量级的大数看起来就是“坐标飞到天上”。我现在拿到任何新环境都会先跑一遍这类场景拾取再往里面堆业务。第一次在这套msvc2017包上写拾取时就因为少了GeoPoint这一步把地心坐标当经纬度输出差点怀疑是地球投影算错了。这个习惯帮我避开了很多“看起来能跑、实际坐标系没对上”的黑匣子问题。这套组合值不值得投入取决于你的方向做三维GIS它足够当底层把精力留给业务如果只是普通桌面软件就不要为地形拖进osgEarth这个依赖。希望帮到你。本文还有配套的精品资源点击获取