SuperMap-Cesium-3D加载倾斜摄影模型:OSGB转3D Tiles与性能调优实战
简介本资源面向GIS开发与三维可视化方向的开发者提供一套将SuperMap与Cesium结合的3D地图展示方案重点解决倾斜摄影测量数据在Web端的三维加载与交互问题。项目以Vue.js为前端框架借助Babel完成语法转换并通过SuperMap-Cesium-3D插件把SuperMap的空间数据管理能力与Cesium的WebGL渲染能力整合适合具备一定前端与GIS基础、希望快速搭建三维场景的读者参考。压缩包共560个文件约25.84MB以png、jpg图片与js脚本为主辅以json、css、vue、html等前端资源另含wasm、terrain、s3m等三维数据与地形文件以及browserslistrc、vue.config.js、babel.config.js、package.json等工程配置目录结构完整。目前已有725人学习下载可帮助读者理解三维地图项目的依赖组织、多浏览器兼容配置与倾斜摄影集成思路。1. 倾斜摄影模型在三维地图里加载失败问题往往出在哪很多做三维 GIS 的同行第一次接触 superMap-Cesium-3D 显示地图、倾斜摄影这条链路时都会遇到一个反直觉的现象数据明明在桌面端能正常打开OSGB 目录也完整可一旦放进 Cesium 场景要么整片模型不出现要么只冒出一个灰白色方块控制台还安静得像个黑匣子。这不是玄学八成是坐标系、根节点位置或者服务发布方式三者之一没对齐。superMap-Cesium-3D 本质是把 SuperMap iServer 或 iClient3D 的三维能力挂到 Cesium 的渲染管线上倾斜摄影则是用无人机多角度采集、经空三加密和纹理映射生成的实景三维模型常见格式是 OSGB 加一个 metadata.xml。两者结合要解决的核心问题只有一个让 Cesium 知道模型在哪、朝哪、多大。适合谁看做智慧园区、城市三维底图、工程进度可视化的前端和 GIS 工程师只要你能跑起一个静态页面就能跟着往下走。2. 从 OSGB 到 3D Tiles倾斜摄影进 Cesium 的完整链路2.1 为什么不能直接把 OSGB 丢给 CesiumCesium 原生只认 3D Tiles不认 OSGB。OSGB 是 OpenSceneGraph 的二进制场景格式一棵树状节点结构每个节点带自己的变换矩阵和纹理引用。Cesium 的渲染循环没法直接遍历这种结构所以中间必须做一次格式转换把 OSGB 的节点树压平成 3D Tiles 的 tileset.json 加一批 b3dm 或 pnts 瓦片。常见做法有两种。第一种是用 SuperMap iDesktopX 直接导入 OSGB再导出为 3D Tiles 或 SuperMap 自己的 S3M 缓存。第二种是用开源工具链比如把 OSGB 转成 glTF 再打包成 3D Tiles。我一般会优先走 iDesktopX因为坐标系配准和 LOD 分层它已经帮你处理好了省掉大量手工调参。这里有个关键概念叫根节点变换。倾斜摄影生成时模型原点往往在测区某个角点甚至直接是投影坐标的原始值比如东坐标 500000、北坐标 3000000 这种量级。Cesium 场景里如果直接把这个坐标当局部坐标用模型会飞到地球外面去。所以转换工具会生成一个 region 或 transform把模型整体平移到 Cesium 的 ECEF 坐标系下。2.2 用 iDesktopX 完成 OSGB 到 3D Tiles 的转换下面这套流程是我在多个模拟项目里反复用过的参数按默认走基本能出结果但有几个地方必须改。第一步新建一个球面场景坐标系选 WGS84。不要选平面场景倾斜摄影最终要贴在地球曲面上。第二步导入 OSGB 数据。在 iDesktopX 里选择「三维数据」→「倾斜摄影」→「导入 OSGB」指向包含 metadata.xml 的目录。# 假设 OSGB 数据在 /data/uav/osgb/ 下 # metadata.xml 同级目录应有 Data 文件夹 /data/uav/osgb/ ├── metadata.xml └── Data/ ├── Tile_000_000/ │ ├── Tile_000_000.osgb │ └── ... └── ...导入时注意 SRS 设置。如果 metadata.xml 里已经写了 EPSG 码iDesktopX 会自动识别如果没写你得手动指定通常是 EPSG:4326 或 EPSG:4547 这类地方坐标系。选错坐标系模型会整体偏移几百米而且旋转角度也不对。第三步生成 S3M 缓存或 3D Tiles。右键数据集选择「生成缓存」参数如下表。参数建议值说明缓存类型S3M 3.0 或 3D Tiles要挂 Cesium 选 3D Tiles瓦片边长128 或 256越小越精细但瓦片数量翻倍纹理压缩WebP 或 JPEGWebP 体积小但老浏览器要 polyfillLOD 层级自动手动设容易断层线程数CPU 核数一半全开容易内存溢出生成完成后你会得到一个 tileset.json 和一堆 b3dm 文件。这个 tileset.json 就是 Cesium 要加载的入口。2.3 在 Cesium 里加载 3D Tiles 并叠加 SuperMap 底图SuperMap-Cesium-3D 的加载方式和原生 Cesium 几乎一样区别在于底图服务地址和认证参数。下面是一个最小可运行示例。// 初始化 Cesium Viewer关闭默认影像和地形 const viewer new Cesium.Viewer(cesiumContainer, { imageryProvider: false, // 先不挂默认底图 terrainProvider: false, // 地形后续单独加 baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, animation: false, timeline: false }); // 挂载 SuperMap iServer 发布的影像底图 const imageryProvider new Cesium.SuperMapImageryProvider({ url: http://localhost:8090/iserver/services/map-world/rest/maps/World, // 如果是加密服务这里加 credential // credential: new Cesium.Credential(token, your-token) }); viewer.imageryLayers.addImageryProvider(imageryProvider); // 加载倾斜摄影 3D Tiles const tileset viewer.scene.primitives.add( new Cesium.Cesium3DTileset({ url: ./tileset.json, // 转换生成的入口文件 maximumScreenSpaceError: 16, // 默认 16调小更清晰但更卡 maximumNumberOfLoadedTiles: 1000, skipLevelOfDetail: true, // 开启 LOD 跳过提升加载速度 baseScreenSpaceError: 1024, skipScreenSpaceErrorFactor: 16, skipLevels: 1, immediatelyLoadDesiredLevelOfDetail: false, loadSiblings: false, cullWithChildrenBounds: true }) ); // 等 tileset 就绪后飞到模型位置 tileset.readyPromise.then(function (tileset) { viewer.zoomTo(tileset, new Cesium.HeadingPitchRange( 0, Cesium.Math.toRadians(-30), 500 )); }).otherwise(function (error) { console.error(3D Tiles 加载失败:, error); });这段代码的逻辑说明先创建一个不带默认底图的 Viewer避免网络请求拖慢首屏然后挂 SuperMap 的影像服务注意 URL 里的服务名和地图名要和你 iServer 上发布的一致最后加载 3D TilesmaximumScreenSpaceError 是核心参数16 是平衡值调到 8 模型更精细但显存占用翻倍调到 32 会明显糊。参数说明skipLevelOfDetail 设为 true 后Cesium 会跳过中间层级直接加载目标层级大场景首屏快很多但快速旋转视角时可能出现短暂空洞。如果你的场景对完整性要求高把它设成 false代价是加载时间变长。2.4 坐标系配准模型偏移和旋转的根因倾斜摄影最常见的翻车现场就是模型位置对不上底图。原因通常有三个一是 OSGB 生成时用的坐标系和 Cesium 底图不一致二是转换时没有正确写入 transform三是模型本身有旋转但转换工具没识别。排查方法在 Cesium 里打开 tileset 的 boundingSphere看中心点坐标是否落在你预期的经纬度附近。tileset.readyPromise.then(function (tileset) { const center tileset.boundingSphere.center; const cartographic Cesium.Cartographic.fromCartesian(center); console.log(模型中心经度:, Cesium.Math.toDegrees(cartographic.longitude)); console.log(模型中心纬度:, Cesium.Math.toDegrees(cartographic.latitude)); console.log(模型中心高度:, cartographic.height); });如果经度纬度差了几度说明坐标系没对上如果高度是负几千说明椭球高和海拔高搞混了。解决方式是在 iDesktopX 里重新指定坐标系并重新生成缓存不要试图在 Cesium 里用 modelMatrix 硬掰那样纹理和光照都会出问题。3. 性能调优让倾斜摄影在浏览器里跑顺的 4 个参数3.1 maximumScreenSpaceError 到底怎么设这个参数决定了一个瓦片在屏幕上占多少像素时才被加载。值越小加载的瓦片越精细显存和带宽消耗越大。经验值桌面端 16移动端 32大屏展示 8。但这不是绝对的如果你的模型纹理压缩率高可以适当调小。我一般会做一个自适应根据设备像素比和屏幕宽度动态设。const pixelRatio window.devicePixelRatio || 1; const screenWidth window.screen.width; let sse 16; if (pixelRatio 2 screenWidth 1920) { sse 8; // 高分屏大屏给足精度 } else if (screenWidth 768) { sse 32; // 移动端保流畅 } const tileset new Cesium.Cesium3DTileset({ url: ./tileset.json, maximumScreenSpaceError: sse });3.2 纹理压缩与显存占用倾斜摄影的纹理是大头一个平方公里测区动辄几十 GB 纹理。转 3D Tiles 时如果纹理不压缩浏览器直接爆显存。建议在 iDesktopX 生成缓存时选 WebP压缩率比 JPEG 高 30% 左右视觉损失很小。如果已经生成了未压缩的缓存可以用工具二次压缩但要注意保留原始纹理的 mipmap 层级否则远处瓦片会闪烁。3.3 用 requestScheduler 控制并发请求数Cesium 默认的并发请求数偏高在弱网环境下反而拖慢首屏。可以手动限制。viewer.scene.requestScheduler.maximumRequests 6; viewer.scene.requestScheduler.maximumRequestsPerServer 4;这两个值调小后首屏瓦片加载更有序不会出现一堆请求排队但一个都没完成的情况。代价是总加载时间可能略长但用户体验更稳。3.4 地形与倾斜摄影的贴合问题如果场景里同时有地形和倾斜摄影模型底部可能会悬空或陷入地面。原因是地形高程和模型高程基准不一致。解决办法是在 iDesktopX 里把模型底部裁剪到地形面或者在 Cesium 里给 tileset 加一个高度偏移。tileset.modelMatrix Cesium.Matrix4.fromTranslation( new Cesium.Cartesian3(0, 0, -2.5) // 向下偏移 2.5 米 );这个偏移量要实测不同测区不一样。我一般会先加载地形再用鼠标拾取模型底部和地形面的高度差取平均值。4. 避坑指南倾斜摄影加载的 5 个血泪教训4.1 模型整体消失控制台无报错现象页面空白Network 里 tileset.json 请求 200但 b3dm 一个都没加载。原因tileset.json 里的 geometricError 设得太大Cesium 认为当前视角不需要加载任何瓦片。解决检查 tileset.json 根节点的 geometricError通常应该在 500 左右。如果超过 10000手动改小。或者用 viewer.zoomTo(tileset) 强制飞到模型位置。4.2 模型出现但全是灰白色没有纹理现象模型几何形状正确但表面是纯色没有照片纹理。原因b3dm 里的纹理引用路径不对或者纹理格式浏览器不支持。解决打开一个 b3dm 文件检查头部 JSON 里的 images 字段看 uri 是否指向了实际存在的文件。如果是相对路径确认和 tileset.json 的相对关系。另外如果纹理是 DDS 格式Cesium 默认不支持要转成 WebP 或 JPEG。4.3 旋转视角时模型闪烁或撕裂现象静止时正常一旋转就出现瓦片闪烁、缝隙。原因LOD 切换时相邻瓦片的几何误差不一致或者 skipLevelOfDetail 导致跳层加载。解决把 skipLevelOfDetail 设为 false同时把 maximumScreenSpaceError 调大一点让 LOD 切换不那么频繁。如果还不行检查生成缓存时是否开启了「纹理压缩」但没开「几何压缩」两者要配套。4.4 加载到一半浏览器崩溃现象加载几十个瓦片后页面卡死内存飙升。原因瓦片数量太多或者单个瓦片太大。常见于瓦片边长设成 512 且没有做 LOD 分层的情况。解决重新生成缓存瓦片边长降到 128 或 256确保 LOD 层级至少 5 级。另外在 Cesium 里开启内存管理。viewer.scene.globe.tileCacheSize 100; // 默认 100可适当调小 viewer.scene.primitives.destroyPrimitives false; // 避免频繁销毁重建4.5 SuperMap 服务跨域导致底图不显示现象3D Tiles 正常但 SuperMap 影像底图一片黑控制台报 CORS 错误。原因iServer 默认没开跨域或者端口不对。解决在 iServer 的 web.xml 里加 CORS 过滤器或者用 Nginx 反向代理统一域名。临时方案是在浏览器启动参数里加 --disable-web-security但仅限本地调试不要带到生产。5. 进阶技巧用裁剪面和动态单体化做交互倾斜摄影本身是一整块 mesh没法直接点选单个建筑。要做交互常见做法是动态单体化在模型上方叠加一层矢量面用面的 ID 关联属性点击时通过 Cesium 的 pick 拿到面再查属性表。// 加载矢量面作为单体化载体 const dataSource new Cesium.CustomDataSource(building); viewer.dataSources.add(dataSource); // 假设从 GeoJSON 加载建筑轮廓 Cesium.GeoJsonDataSource.load(./buildings.geojson, { clampToGround: true }).then(function (ds) { viewer.dataSources.add(ds); const entities ds.entities.values; for (let i 0; i entities.length; i) { const entity entities[i]; // 给每个面加一个半透明填充便于点击 entity.polygon.material Cesium.Color.BLUE.withAlpha(0.3); entity.polygon.outline true; entity.polygon.outlineColor Cesium.Color.CYAN; // 绑定属性 entity.properties { buildingId: B i, name: 模拟建筑 i, height: 30 Math.random() * 50 }; } }); // 点击拾取 const handler new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction(function (click) { const picked viewer.scene.pick(click.position); if (Cesium.defined(picked) picked.id) { const props picked.id.properties; if (props) { console.log(选中建筑:, props.name.getValue()); console.log(建筑高度:, props.height.getValue()); } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK);这段代码的关键在于 clampToGround 要设 true让矢量面贴合地形填充透明度别太高否则挡住倾斜摄影纹理。点击拾取时Cesium 会优先返回最上层的 entity所以矢量面要放在 tileset 之上。另一个进阶用法是裁剪面。比如只看某一栋楼内部可以用 ClippingPlaneCollection 把外部裁掉。const clippingPlanes new Cesium.ClippingPlaneCollection({ planes: [ new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), 0.0) ], edgeWidth: 1.0, edgeColor: Cesium.Color.WHITE }); tileset.clippingPlanes clippingPlanes;这个平面方程的意思是保留 X 轴正方向一侧负方向被裁掉。实际用的时候平面法向量和距离要根据建筑位置算通常配合鼠标交互动态更新。最后说一个我自己的习惯每次调完参数先把 maximumScreenSpaceError 临时设成 64让整个场景快速加载出来确认位置和姿态没问题再逐步调小到目标值。这样能避免在参数没对齐的情况下反复等加载省下大量时间。希望帮到你。本文还有配套的精品资源点击获取