做 Cesium 项目做到第三个月,我被一块地形绊倒了。那是一个智慧园区项目,楼层、道路、植被、管线都摆得整整齐齐,但领导把视角往地面一压,所有车辆都陷在柏油路面下,楼层地板全部裸露在绿化带上,画面惨不忍睹。问题很简单——项目一开始根本没有加载任何高程数据,整个场景是贴在一个光滑的参照椭球面上的。从那以后我养成了个习惯:只要有 Cesium 项目,第一件事一定是先把高程数据链路跑通,再谈模型、图标、业务功能。这篇就把 Cesium 高程数据从选型、加载到取高程值的完整链路,按我的实操顺序从头讲一遍,适合刚开始接触地形数据的 Cesium 开发,也适合那些已经加载了地形却被各种古怪问题折磨的同行。
1. 没有高程数据,Cesium 只是张糊了贴图的皮球
1.1 高程数据在 Cesium 里到底管什么
高程数据在 GIS 领域的正式叫法是 DEM(Digital Elevation Model),它本质上就是一组记录地面格网点海拔高度的数据。别小看这层高度信息,Cesium 的地形渲染、贴地摆放、通视分析、淹没分析、航线规划全部建立在它之上。Cesium 拿到高程数据后,会把它构造成带细节层次(LOD)的三角网地形,再叠加影像瓦片作为纹理,最终才呈现出我们肉眼看到的起伏山川。
这么说可能有点抽象,换个角度:Cesium 默认情况下如果不配置任何地形,整个地球就是基于 WGS84 椭球体数学表面生成的光滑球面。这个椭球面不是真实地面,它只是描述地球形状的一个数学近似。没有高程数据时,你在上面摆放建筑、车辆、管线,这些物体全都贴在了一个“虚拟光滑地面”上,而不是实际的坡面、沟谷、山脊上。一旦视角压低,穿模、浮空、陷地的效果就会全部暴露。
日常开发中高程数据至少影响四类事情:
- 地表渲染出来的起伏效果,决定场景真实感;
CLAMP_TO_GROUND贴地实体的落点位置,例如一辆车在地形坡面上必须跟着倾斜;- 相机飞行、跟踪目标时离地面的相对高度,航线会不会撞山就看它的;
- 光照阴影、可视域、淹没分析等业务计算,没有真实地表这些全是空中楼阁。
1.2 地形数据在渲染管线里的位置
Cesium 的渲染体系里,高程数据通过TerrainProvider接口接入,最常用的是CesiumTerrainProvider。它负责把高程瓦片变成三角形网格,再把网格交到 Globe 的地形四叉树(QuadtreePrimitive)里统一管理。地形网格加载完成之后,影像图层会像贴纸一样覆盖在网格表面,灯光、雾效、阴影再叠加在网格上。
这里有个关键点:地形网格是不分图层的,它和影像图层的加载是两条并行管线。影像只需要一张 PNG,地形却需要精确的高度字段,所以地形瓦片格式和普通影像格式完全不同。Cesium 的量化网格地形会自动根据相机距离决定加载哪一层 LOD,近处加载高分辨率网格,远处加载低分辨率网格,这就是地形四叉树的核心逻辑。理解这个机制之后,你再去调地形参数就知道改的是什么东西,而不是瞎试。
1.3 地形缺失时最常见的三种翻车现场
我先列出自己实际遇到过的三种情况,把你可能踩的坑提前摆出来:
第一,模型集体陷地或浮空。园区里所有建筑底部都陷进山体半截,或者整个楼悬在半空。原因是模型的高度是用某一固定高程写死的,但地表起伏让它实际交点的位置偏了。这类问题最迷惑人,因为从正视角看不出毛病,一旦斜视角拉近就原形毕露。
第二,相机视角穿山。用户点击一个远处目标,flyTo 过去后镜头直接钻进了山体内部,屏幕一片黑。没有高程数据的场景下,椭球面上飞行永远畅通无阻,加载真实地形后这条路径可能正好从山肚子里穿过去。
第三,业务分析数据完全失准。比如做雷达遮蔽分析时,两个点之间明明有山体挡住,系统却说通视;做淹没分析时,水淹到哪里根本算不出来。这些本质上都是因为高程基准缺失,所有三维位置都在一个假表面上互相“错位”。
这三个翻车现场背后是同一个根因:没有把地理空间数据的第三维——高程——纳入项目的基础设施。所以接下来我们看看怎么选一个靠谱的高程数据源。
2. 选对数据源和格式,后面能省一半时间
2.1 去哪拿高程数据
高程数据的来源五花八门,但生产项目里用下来,主流其实就是下面几类:
- SRTM:NASA 测绘的全球数据,分辨率有 1 弧秒(约 30 米)和 3 弧秒(约 90 米)两档,覆盖全球大部分陆地区域,免费可商用,适合做区域级场景。
- ASTER GDEM:也是全球覆盖,分辨率 30 米,但局部区域噪声略明显,用它做城市级精细场景会有点吃力。
- ALOS AW3D30:日本 JAXA 发布的全球 30 米数据,在山地区域的细节表现通常比 SRTM 更稳,免费开放,是很多国内外项目的主力数据。
- 地方高分 DEM:国内很多城市有 1 米到 5 米分辨率的商业 DEM 或 DSM,这类数据通常来自测绘部门,精度高但获取和授权流程复杂,适合城市级、园区级的正式交付项目。
我的建议是:Demo 演示、验证功能用全球免费数据就够了;但如果是给政企客户做交付,千万要用经过当地测绘部门检验的高分辨率数据,全球免费数据在关键区域出现几米到几十米的偏差,在验收时很难交代。
实际选数据时要同时考虑覆盖范围、分辨率、时效性和授权,这四个维度缺一个都会在后面出问题。授权这点尤其容易被忽略——很多朋友从某个网盘随手下了个 GeoTIFF 就开始做,做到一半发现没法上线公网,非常被动。
2.2 栅格高程和量化网格:两种格式的取舍
拿到以上数据源之后,原始文件通常是 GeoTIFF、HGT、DTED、IMG 这类栅格高程格式。它们的特点是每个像素记录一个高度值,适合做分析,但 Cesium 前端不能直接流式加载这种大栅格。Cesium 真正推荐的是 quantized-mesh(量化网格)格式的地形切片——一组带 LOD 的三角形网格,四叉树切分、按需加载。
这两种格式的取舍可以从下面这张表看出端倪:
| 格式 | 典型来源 | 特点 | Cesium 直接支持情况 |
|---|---|---|---|
| GeoTIFF | SRTM、ASTER GDEM、商业 DEM | 栅格高度场,分析友好,文件大 | 需转切片 |
| HGT | SRTM 官网 | 按经纬度分块,无压缩 | 需转切片 |
| DTED | 测绘军事数据 | 分级标准,Level 0/1/2 | 需转切片 |
| quantized-mesh | Cesium 地形切片 | 三角网 + 四叉树 LOD,流式加载 | 通过 CesiumTerrainProvider 直接加载 |
| cesium terrain | Cesium ion 生成 | 含法线、水纹等增强数据 | 直接加载 |
从栅格到量化网格的转换,会顺手完成几件事:一是把连续高度场离散成自适应三角网,平地少三角形、山地多三角形;二是按四叉树切瓦片,前端可以按需请求;三是生成法线贴图和水体蒙版等增强数据。所以哪怕你手里只有一张 GeoTIFF,也要有把它变成地形瓦片再加载的思路,而不是直接丢给浏览器。
2.3 分辨率怎么选:30米、10米还是1米
分辨率代表每个采样点的间距,间距越小地形细节越丰富。全球免费数据大多是 30 米,做省市级宏观场景完全够用;但你要是做单栋建筑级别的精细园区,30 米的数据连楼顶的轮廓都表达不出来,必须换 1 米级别的 DSM 或 DEM。
还有一种很容易混淆的情况:DSM 和 DEM 的区别。DEM 是纯地面高程,把建筑和树都削平了;DSM 是地表以上所有东西的高程,包含屋顶、树冠。可视化场景如果追求“楼是楼、地是地”的效果,DSM 反而会把建筑和地形混在一起造成穿模。所以做建筑贴合、道路铺装时优先选 DEM,做树冠遮挡、无人机航线规划时 DSM 更真实。别拿到一份数据就开干,先问清楚它到底是哪个。
2.4 我对数据源的推荐组合
如果是快速原型验证,直接用 Cesium ion 的全球地形(World Terrain),一行代码就能加载,50 米甚至更细的细节都能拉起来,省心省事。如果是国内正式项目又想控制成本,我一般这样搭配:
- 区域大背景用 ALOS AW3D30 转好的地形切片,免费且覆盖广;
- 重点城市或园区中心,单独采购或复用当地 1 米到 5 米的 DEM 做局部高精度地形;
- 多个精度的地形切片通过 Cesium 的多个 TerrainProvider 切换或拼接,实现“宏观能看到全貌,微观能看到细节”的效果。
这套组合最大的好处是花钱花在刀刃上,全球 30 米免费数据铺底,重点区域用高精度商用数据替换,视觉和预算都兼顾。
3. 把高程数据挂到 Cesium 里的几种接法
3.1 最省事:Cesium ion 全球地形
如果你只是想在项目里快速看到起伏地形,Cesium ion 的全球地形是最快路径。代码极短:
const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId(1), });这里的 asset id 1 就是 Cesium World Terrain,它包含了全球范围内的量化网格地形,还附带法线和水纹数据,显示效果很惊艳。Cesium ion 免费额度基本够开发测试用,但正式上线如果请求量大,还是建议评估一下成本,或者干脆把地形瓦片同步到自己的服务器上。
这里提醒一句:Cesium 新版本里CesiumTerrainProvider的构造函数已经改为异步工厂方法fromUrl()/fromIonAssetId(),老版本里new Cesium.CesiumTerrainProvider({ url })的写法在新版本里会直接报错。如果你从网上抄了一段老代码发现地形一直加载不了,先检查是不是这个原因。
3.2 本地化:GeoTIFF 转地形切片的完整链路
国内生产环境最稳妥的做法,还是把高程数据转成 terrain 格式放在自己的对象存储或 CDN 上。转换工具链我实测下来比较顺的是这条:
- 先把 GeoTIFF 等原始数据用 GDAL 重投影到 WGS84 / EPSG:4326,统一坐标基准;
- 再使用支持 quantized-mesh 输出的工具生成地形瓦片。常见的工具包括 Cesium ion 的上传转换、开源社区的 Cesium Terrain Builder、以及一些商业 GIS 软件的地形切片模块;
- 把生成的
tileset.json和瓦片目录部署到任意静态服务器或 OSS; - 前端通过
CesiumTerrainProvider.fromUrl()加载。
const terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl( "https://your-cdn.example.com/terrain/", { requestVertexNormals: true, requestWaterMask: true, } ); const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: terrainProvider, });requestVertexNormals这个参数很多人忽略,但如果你需要地形受光照影响产生明暗面,或者做地形压平和动态光照效果,必须开启它。requestWaterMask同理,想要海岛海岸线或者模拟水面效果就必须开。这两个参数会额外请求法线瓦片和水纹瓦片,体积和请求数会增加,但视觉提升很明显,值得。
3.3 数据服务型:ArcGIS 地形服务和其他远程地形
除了自建瓦片和 Cesium ion,生产环境中也经常对接现成的地形服务。ArcGIS 的 ImageServer 里很多高程服务可以通过ArcGISTiledElevationTerrainProvider直接接入。这类方式的优点是不用自己处理转换,缺点是对服务端性能依赖很大,瓦片请求不稳定时地形加载会一卡一卡的。
还有一个不能忽视的方向是 Cesium for Unreal、Cesium for Unity 这类引擎插件。虽然它们的 API 和 Web 版本不完全一样,但高程数据的源头和瓦片切分机制是共通的。你在 Unreal 里看到的山脉和 Web 端其实是同一套地形瓦片,理解了 Web 端的加载逻辑,再去看引擎插件的 CesiumTerrainProvider 配置就不会发怵。
3.4 地形 Provider 切换的细节
换地形最让人抓狂的一个问题是:把viewer.terrainProvider赋了新值之后,画面并不会立刻刷新重绘,尤其是你开了requestRenderMode: true时,场景会停在那里纹丝不动。这个不算 bug,而是 Cesium 的渲染循环被优化了,没有事件驱动就不会重绘。解决方法是手动触发一次渲染:
viewer.scene.requestRender();另外,切换地形 Provider 时,原本使用CLAMP_TO_GROUND的实体位置会在一瞬间重新计算,表现就是模型先轻微跳动再落定。如果你的业务对位置敏感,建议在切换完成前先把相关实体隐藏,等terrainProvider的首个瓦片加载完成后再显示。判断首个瓦片是否就绪,可以通过检查viewer.scene.globe.tilesLoaded或者监听地形 provider 的 error 事件来实现。
4. 取高程值的 API 用了不少,真正好用的就这几个
4.1 sampleTerrainMostDetailed:批量采样地形
很多业务场景并不是要渲染地形,而是要在某个经纬度上拿到地形高度。比如放置设备、无人机航线规划、土方计算,都需要批量采样高程。Cesium 官方 API 里最顺手的函数就是Cesium.sampleTerrainMostDetailed。
const positions = Cesium.Cartographic.fromDegreesArray([ 116.391, 39.907, 121.473, 31.230, ]); const updated = await Cesium.sampleTerrainMostDetailed(terrainProvider, positions); updated.forEach((p, i) => { console.log(`第${i + 1}个点地形高度:${p.height.toFixed(2)} 米`); });这个方法会自动请求当前区域能拿到的最高细节层级,返回的Cartographic对象里height字段就是该点地形高程。注意它是一批一批异步抓瓦片,如果一次采样的点数非常多,网络请求会瞬间拉满。我一般会把几千个点切成小批次,每批 50~100 个并发,避免把地形瓦片服务打崩。
还有一个旧版 APICesium.sampleTerrain,需要手动指定 level 参数。 level 太小采样不准, level 太大请求瓦片数量爆炸,坑不少。新项目直接放弃它,用sampleTerrainMostDetailed就好。
4.2 clampToHeight:把模型精准“钉”在地上
另一类高频需求是给模型找一个精确接地的高度。最简单的方式是设置heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,让 Cesium 自动计算,但它实际上会占用额外的渲染进程,实体一多性能就会掉。生产项目里我更推荐手动算好高度再摆放模型。
const carto = Cesium.Cartographic.fromDegrees(116.391, 39.907); const height = await viewer.scene.clampToHeight(carto, [], 1); if (Cesium.defined(height)) { // 用这个 height 创建实体 viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.391, 39.907, height), model: { uri: "device.glb" }, }); }clampToHeight的第二个参数是objectsToExclude,第三个参数是采样宽度,默认是 1 像素。这里有一个细节:如果场景里同时有 3D Tiles 建筑物,clampToHeight 默认会连建筑几何体一起采样,导致模型被“钉”在楼顶而不是地面。这种情况下你必须在第二个参数里把建筑 tileset 传进去排掉。
const height = await viewer.scene.clampToHeight(carto, [buildingTileset], 1);踩过这个坑的人应该不少——摆了半天的设备全部跑到了屋顶上,排查到半夜才发现是这个参数的问题。
4.3 scene.pickPosition 和 camera.pickEllipsoid:鼠标拾取隐藏的高程
鼠标点击场景获取地表位置,是所有交互功能的地基。viewer.scene.pickPosition(movement.position)可以把屏幕像素反算成世界坐标,如果该点恰好落在地形网格上,就能拿到包含高程的准确坐标。但它有个适用前提:必须开启viewer.scene.globe.depthTestAgainstTerrain = true,否则 Cesium 不知道你要拾取的是地形还是背景,拾取结果经常是一个莫名其妙的椭球面点。
如果你只是想要一个“鼠标点一下,告诉我这块地海拔多高”的简单功能,还有一种更稳的做法:先用camera.pickEllipsoid拿到椭球面上的经纬度,再用sampleTerrainMostDetailed取高程。这样不依赖深度缓冲,也不会受建筑遮挡影响,缺点是稍慢一点,但在交互平移到地形加载完成后误差很小。
这两条路线的取舍很简单:场景交互要实时、要准,用pickPosition;后台计算做分析,用sampleTerrain或clampToHeight。混着用容易把自己绕晕,建议一个项目里统一好。
4.4 这些 API 的适用场景对照表
| API | 返回值 | 适用场景 | 注意事项 |
|---|---|---|---|
| sampleTerrainMostDetailed | 地形高程数组 | 批量采样、航线规划、后台分析 | 大批量要分批请求 |
| clampToHeight | 贴地后的高度 | 放置模型、获取贴地位置 | 注意排除 3D Tiles 建筑 |
| viewer.scene.pickPosition | 世界坐标 | 鼠标拾取、编辑交互 | 需要开启深度测试 |
| camera.pickEllipsoid | 椭球面坐标 | 快速求经纬度 | 不含真实地形高度 |
| HeightReference.CLAMP_TO_GROUND | 自动贴地 | 少量展示性实体 | 实体过多时性能下降 |
5. 高程数据落地时,我踩过的那些坐标系和精度坑
5.1 椭球高、正高、海拔:三个“高度”不是一回事
这个坑坑了很多人,因为它藏得很深。我们常说的“海拔”是基于大地水准面的正高,GPS 设备返回的高度则是相对于 WGS84 椭球面的椭球高,两者之间差一个大地水准面差距(geoid undulation)。在中国区域,这个差距大概在负十米到正三十米之间浮动,不同地方差异还不一样。
也就是说,如果你手里有一份高程数据标注着“海拔 500 米”,但 Cesium 地形内部用的是 WGS84 椭球高,那两者直接在高度上就差了十几米到几十米。模型摆上去后整体偏高或偏低,就是数据基准没对齐。
怎么判断你的数据到底用的哪个基准?最直接的办法是取一个已知控制点做比对。比如找个 GPS 实测点,把它的椭球高和这份高程数据在该点的值相减,如果差值稳定在一个常数附近,就把它作为整体偏移修正值加进去。Cesium 也提供了geoidHeight相关的支持,但从我的经验看,项目中更常用的是先确认数据定义,再做统一偏移,逻辑更简单可控。
5.2 中国区域内 CGCS2000 和 WGS84 的细微差异
中国目前的法定测绘基准是 CGCS2000,它和 WGS84 在大多数应用场景下可以近似等同,但严格来说两个椭球的定义参数并不完全一致。在实际坐标转换中,平面位置差一般是厘米级,小比例尺地图根本看不出来,但在城市级高精度项目中,这个厘米级差异可能会反应在建筑边缘和地形对齐的微小偏差上。
更麻烦的是高程基准。CGCS2000 框架下的高程通常采用 1985 国家高程基准,它的零点定义和全球大地水准面也不是完全一致,在西部局部地区差异会更明显。对 Cesium 这种国际上以 WGS84 为默认基准的引擎来说,国内数据直接叠加时要做严密的基准核查,最保险的方式还是拿着项目区域的控制点矩阵做一次全面的高程匹配测试,别凭感觉给一个固定偏移值。
5.3 地形加载不刷新、模型浮空、地形闪烁的排查路径
我把开发中最容易遇到的三个问题集中排查一下,因为它们的表象非常相似,经常被混为一谈。
地形加载后画面不刷新,通常就是渲染循环没有被触发。排查步骤很固定:先看是否开了requestRenderMode,开了就先scene.requestRender()手动触发;再看是否用了异步创建 terrainProvider,但赋值之前没等它返回;最后看是不是每次切换 Provider 后旧瓦片和新瓦片没有清理干净。
模型整体浮空,多半是高程基准问题,也就是上一小节说的椭球高和正高的差异。如果整体浮空高度在所有地方都差不多,那基本可以确定是固定偏移;如果高差随地形起伏变化,说明数据本身基准就分叉了,需要做区域级别的校正。
地形闪烁则优先怀疑深度测试和 LOD 切换导致的 z-fighting。打开深度测试后闪烁反而更明显,说明是相邻 LOD 层级之间的几何误差造成了表面重叠。这类问题可以从两个方向解决:一是调整相机的maximumScreenSpaceError,让 LOD 切换更平滑;二是检查是否存在多个贴地实体和地形完全共面,给实体加一个微小的高度偏移(例如 0.05 米)即可消除闪烁。
5.4 大范围场景下的高程精度与性能平衡
全球级场景直接加载最高细节地形是灾难性的,浏览器可能瞬间崩掉,瓦片请求堆积如山。这里有个热词我经常在交流群里看到:Cesium 3D 地球滚动出现崩溃。很多崩溃并不是 Cesium 本身的问题,而是地形瓦片请求量瞬间爆发,或者 3D Tiles 和地形瓦片同时抢带宽,把服务器和浏览器一起拖垮。
解决办法不外乎几板斧:限制地形最大 LOD 层级,不要无限请求高层级瓦片;调大maximumScreenSpaceError,让远处用更粗糙的网格;合理设置viewer.scene.globe.tileCacheSize,避免内存无限增长;必要时预加载重点区域的地形瓦片,手动控制请求节奏。
性能优化的本质是告诉 Cesium“哪里重要多花资源,哪里不重要省着点”,这和业务需求强相关——你来之不易的 1 米地形不要在整个地球范围内全量加载,只在你关心的园区边界内做高精度,其他区域用 30 米全球数据兜底,这是性价比最高的布置。
6. 别忘了这些业务场景才是高程数据的真正出口
6.1 沿地形飞行的航线规划
航拍、巡检、路径巡航这些功能都要让相机贴着地形往前走,不能撞山也不能飞得太高。实现思路很朴素:先在路径上均匀插值一串经纬度点,用sampleTerrainMostDetailed批量取高度,给每个点加上一个离地偏移量,再把结果喂给SampledPositionProperty驱动相机。这样相机就能沿着地表起伏走,高度始终保持在地面上方一个固定值。
不过有个细节新手容易忽略:取到的地形高度序列直接驱动相机时,相机高度会在每个采样点之间“咔咔”跳,因为采样点是离散的,高度差会形成台阶。解决办法是对高度序列做滑动平均平滑,或者用 Catmull-Rom 样条插值,让相机高度曲线连续可导。实际飞行效果是否顺滑,和这一步的处理精细度直接相关。
6.2 通视分析和雷达遮蔽计算
做雷达、通信、监控布点这类项目时,通视分析是核心算法。算法本身不复杂:在两点之间线性插值 N 个采样点,每个点算一条视线内插高度,再和该点的地形高度比较,一旦视线高度低于地形高度,就说明视觉被遮挡了。
这里最关键的细节是插值高度和地形高度必须在同一高程基准下比较,否则整个判定全是错的。另外采样点数量要足够,山地地形插值太稀会漏掉关键遮挡点,平原可以适当稀疏。这些参数都需要根据具体区域的地形起伏反复调,没有统一标准。
对于 Cesium 项目,还可以把 Ray 和地形做真正的三维求交,比插值更精确,但实现成本高一些。折中方案是先用 Cesium 的scene.pickFromRay做粗筛,再做细插值判定,效率和精度都能兼顾。
6.3 淹没分析思路
洪水淹没、雨涝分析这类需求通常有两条可视化路径。一条是在 GPU 上做高程比较:给定一个水位高度,遍历地形网格顶点,凡是低于水位的顶点就标记为淹没区。这样性能好,适合实时调整水位高度看效果。
另一条是分析级输出:把淹没范围导出成面要素,这就需要先生成水位等值线,再做等值线和地形范围的空间裁剪。这个方案可以用 Cesium 的等值线工具配合 turf 或者 shp 工具链实现,输出结果可以直接给客户做二开分析。
做这类功能时一定要盯住一个核心假设:水位高度和地形高度是否在用同一个高程基准。这看起来是小事,但处理不好就会出现“水从山上流过”的诡异现象,客户可不管你是椭球高还是正高,看到效果不对就是你的锅。
6.4 和 3D Tiles 单体化结合时的注意点
最后聊一个和 3D Tiles 单体化相关的结合点,因为很多项目同时使用了单体化和地形高程。单体化建筑的底部高程如果不贴合地形,建筑不是陷进山坡就是悬在坡外。处理方案通常是在单体化数据预处理阶段,用建筑底面中心点去采样地形高程,把采样结果作为建筑模型的基础高程写入属性。如果地形有明显坡度,还要考虑建筑底面的旋转倾斜对齐,否则只看底面中心点仍然会穿模。
另一个推进方向是“地形压平”:在重点园区里把地形面强制压到某个统一标高,让建筑底面和道路完全平整衔接。这个功能做起来比听起来麻烦一些,需要修改地形瓦片或使用 3D Tiles 的地形修改接口,但场景效果非常专业。工程化落地时我一般结合项目需求决定是否对园区边界内的地形做局部压平,能解决大量“模型和地形打架”的问题。
最后分享一个我自己坚持了很久的工作习惯:每个新项目开工前,先写一个高程数据健康检查脚本,把项目区域内的地形采样一遍,输出高度范围、极值、是否存在空洞和异常凸起。别跳到业务功能开发之后才发现地形数据有问题,到那时候再回头换数据源,改动的成本会成倍增加。高程数据这种东西,越早验证,后面的坑越少。