如果你最近升级了Cesium到1.107版本,可能会遇到一个让人头疼的问题:viewer.terrainProvider突然报错了。这不是你的代码写错了,而是Cesium团队对地形加载API进行了重大重构。作为一个长期使用Cesium的老玩家,我完全理解这种变化带来的困扰,但换个角度想,这种重构其实是为了解决一些长期存在的问题。
在1.107版本之前,地形加载的方式确实存在几个痛点。首先,viewer.terrainProvider的用法过于直接,缺乏必要的异步处理机制,这在加载大型地形数据时经常会导致界面卡顿。其次,旧API的设计没有很好地考虑错误处理和资源加载状态管理,开发者需要自己处理很多边界情况。最重要的是,随着Cesium支持的地形数据源越来越多样化,旧接口的扩展性已经跟不上需求了。
新引入的createWorldTerrainAsync和Cesium.Terrain.fromWorldTerrain()等方法,正是为了解决这些问题。它们采用了更现代的异步设计模式,内置了错误处理机制,并且为未来的功能扩展预留了空间。虽然短期内需要适应新API,但从长远来看,这种改变会让我们的代码更健壮、更易维护。
在1.107版本之前,我们通常这样加载地形数据:
javascript复制// 直接创建地形提供者
viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
url: 'https://assets.agi.com/stk-terrain/world',
requestWaterMask: true,
requestVertexNormals: true
});
// 或者使用快捷方式
viewer.terrainProvider = Cesium.createWorldTerrain();
这种方式简单直接,但存在几个问题:没有考虑地形数据加载的异步性;错误处理完全交给开发者;当需要加载多个地形源时,管理起来比较麻烦。
1.107版本引入了几个关键变化:
viewer.terrainProvider的直接赋值方式createWorldTerrainAsync异步创建方法Cesium.Terrain.fromWorldTerrain()工厂方法迁移到新API时,我们需要特别注意这些变化。最直接的迁移方式是将原来的同步调用改为异步方式:
javascript复制// 新版推荐方式
const terrainProvider = await Cesium.createWorldTerrainAsync({
requestWaterMask: true,
requestVertexNormals: true
});
viewer.terrainProvider = terrainProvider;
对于大多数项目,使用默认的世界地形就足够了。新版API提供了更简洁的写法:
javascript复制// 最简单的方式
viewer.terrainProvider = await Cesium.createWorldTerrainAsync();
// 或者使用工厂方法
viewer.terrainProvider = Cesium.Terrain.fromWorldTerrain();
这两种方式的主要区别在于:createWorldTerrainAsync是真正的异步操作,会返回一个Promise,而fromWorldTerrain是同步的工厂方法。如果你的应用对地形加载时机敏感,建议使用异步方式。
当需要使用非官方的地形数据源时,可以这样配置:
javascript复制const customTerrain = await Cesium.CesiumTerrainProvider.fromUrl(
"//data.mars3d.cn/terrain",
{
requestWaterMask: true,
requestVertexNormals: true
}
);
viewer.terrainProvider = customTerrain;
这里有几个实用技巧:
requestWaterMask,这样能获得更好的水面效果requestVertexNormals可以提升地形光照效果,但会增加数据量如果你的项目需要同时支持新旧版本,可以这样写:
javascript复制function setTerrainProvider(viewer, options) {
if (typeof Cesium.createWorldTerrainAsync === 'function') {
// 新版本
return Cesium.createWorldTerrainAsync(options)
.then(provider => {
viewer.terrainProvider = provider;
});
} else {
// 旧版本
viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
url: Cesium.IonResource.fromAssetId(1),
...options
});
return Promise.resolve();
}
}
地形加载对性能影响很大,这里分享几个实测有效的优化技巧:
javascript复制// 带重试机制的地形加载
async function loadTerrainWithRetry(viewer, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const provider = await Cesium.createWorldTerrainAsync();
viewer.terrainProvider = provider;
break;
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
当地形显示不正常时,可以尝试这些调试方法:
javascript复制// 调试地形加载
viewer.scene.terrainProvider.errorEvent.addEventListener(err => {
console.error('地形加载错误:', err);
});
在实际项目中,我遇到过地形加载缓慢的问题,后来发现是因为同时请求了太多高精度瓦片。通过限制最大细节层级,性能得到了显著提升。这也提醒我们,新API虽然好用,但仍需要根据实际场景合理配置。