经纬志
首页 / 实战手册 / 3D 与 Cesium

模型悬空或陷进地形,差的那几米在哪

先看结论

坐标填的是地面点,模型却飘在半空十几米;换成另一个视角,又有一半埋进了山体。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文
模型悬空或陷进地形,差的那几米在哪 的可运行示例截图

现象

同一份 model.glb,在没有地形时贴地正常,打开世界地形后底部悬空约 8 米; 换到山区,模型下沿被地形切掉一半。打印 position 的经纬度和高度, 数值与设计图上的点位完全一致。

原因

这里叠了三个独立因素:

因素表现量级
模型原点不在底面整体上浮或下沉等于模型包围盒的最小 Y
高度写死为 0地形抬高后陷进去当地地形高程,几十到几百米
地形夸张系数同一坐标在两套地形下位置不同terrainExaggeration 倍数

heightReference 只在实体位置的高度为 0 时才按地表贴合, 如果模型自身原点在体块中心,贴地之后仍然有一半在下面。

排查过程

先量模型自身:加载完成后读包围球,算出最低点相对原点的偏移。

var entity = viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(116.391, 39.907, 0),
  model: { uri: '/models/tower.glb' }
});

viewer.scene.globe.tilesLoaded = true;
entity.model.readyPromise.then(function (model) {
  var bs = model.boundingSphere;
  console.log('包围球半径', bs.radius);
  console.log('中心相对原点', Cesium.Cartesian3.subtract(
    bs.center, entity.position.getValue(viewer.clock.currentTime), new Cesium.Cartesian3()
  ));
});

再量地面:不要在 Viewer 构造完立刻采样,此时地形瓦片还没到。 等 globe.tilesLoaded 为真,或用 sampleTerrainMostDetailed 主动请求该点的高程。

var terrain = await Cesium.createWorldTerrainAsync();
var carto = Cesium.Cartographic.fromDegrees(116.391, 39.907);

var samples = await Cesium.sampleTerrainMostDetailed(terrain, [carto]);
console.log('地面高程', samples[0].height);

解决方案

方案一:让 Cesium 负责贴地,同时把模型原点偏移掉。适合原点在底面的模型:

var position = Cesium.Cartesian3.fromDegrees(116.391, 39.907, 0);

viewer.entities.add({
  position: position,
  model: {
    uri: '/models/tower.glb',
    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
    minimumPixelSize: 48
  }
});

方案二:自己算高度,把模型最低点压到地面上。适合需要精确控制、或原点在中心的模型:

var groundHeight = samples[0].height;
var bottomOffset = -12.4;        // 包围盒最低点,加载后实测得到
var placeHeight = groundHeight - bottomOffset;

var matrix = Cesium.Transforms.eastNorthUpToFixedFrame(
  Cesium.Cartesian3.fromDegrees(116.391, 39.907, placeHeight)
);

var model = viewer.scene.primitives.add(
  Cesium.Model.fromGltf({ url: '/models/tower.glb', modelMatrix: matrix })
);

方案三:如果地形开了夸张,把 terrainExaggeration 与模型高度一起调整, 否则模型会随地形被拉伸而再次错位。

viewer.scene.globe.terrainExaggeration = 1.0;   // 先归零核对,再决定是否开启

可运行示例

复现器保留了本页的故障输入:坐标填的是地面点,模型却飘在半空十几米;换成另一个视角,又有一半埋进了山体。点「修复后」可以看到同样的输入在正确配置下的结果。

问题复现器新窗口打开

修复后:方案一:让 Cesium 负责贴地,同时把模型原点偏移掉。适合原点在底面的模型:

验证

在模型底部加一个 heightReference: CLAMP_TO_GROUND 的小圆点作为基准, 切换 2D/3D 视角观察,圆点与模型底面应始终重合。 把相机降到贴近地面,模型底部与地形之间不应出现可见缝隙或穿插。

常见误判:在 viewer 创建后立刻用 scene.sampleHeight 取高度,拿到的是椭球面高度 0,而不是地形高度。必须等地形瓦片加载,或改用 sampleTerrainMostDetailed。

引用本文

经纬志(2026)。《模型悬空或陷进地形,差的那几米在哪》。https://www.jingweizhi.com/cookbook/cesium-model-float-sink.html(访问日期 2026-11-03)。