📝 写在前面

最近在做一个 CesiumJS 三维地球项目,遇到了两个让人抓狂的问题:

  1. 😫 3D Tiles 模型不停加载卸载:打开 F12 Network,发现 .cmpt.b3dm 文件像疯了一样反复请求
  2. 💥 readyPromise 报错tileset.readyPromise.then is not a function

经过两天两夜的调试,终于找到了根本原因和完美解决方案。本文将详细记录这次踩坑经历,希望能帮助到同样遇到这些问题的同学。


🎯 问题一:3D Tiles 频繁加载卸载

🔍 现象描述

📡 Network 面板:
/dixing/tileset.json          ✅ 加载1次
/dixing/tiles/0/0/0.b3dm     🔄 反复加载/卸载
/dixing/tiles/0/0/1.cmpt     🔄 反复加载/卸载
... (无限循环)

控制台输出:

🔄 Tile 加载: Tile {...}
❌ Tile 卸载: Tile {...}
🔄 Tile 加载: Tile {...}
❌ Tile 卸载: Tile {...}
... (每帧都在重复)

🧠 问题诊断

通过排查,发现这个问题不是代码逻辑错误,而是数据本身的问题

✅ 正常模型的特征:
  • 几何误差值(geometricError)合理分布
  • 包围盒(boundingSphere)计算准确
  • 瓦片金字塔层级合理
❌ 问题模型的特征:
  • 模型体积巨大(几百MB甚至GB级别)
  • 几何误差值设置不合理(过小导致Cesium不断尝试加载更精细瓦片)
  • 包围盒范围异常(导致可见性判断错误)

💊 解决方案

1️⃣ 核心配置:调整屏幕空间误差
const tileset = await Cesium.Cesium3DTileset.fromUrl("/dixing/tileset.json");

// 🎯 黄金三连配置 - 对大模型有奇效
tileset.maximumScreenSpaceError = 128;  // 默认16,调大减少加载频率
tileset.skipLevelOfDetail = true;       // 开启LOD跳级
tileset.baseScreenSpaceError = 1024;    // 基础误差设大
2️⃣ 内存管理:合理分配缓存
// 📦 内存配置(根据模型大小调整)
tileset.maximumMemoryUsage = 2048;      // 内存上限 2GB
tileset.maximumCacheOverflowBytes = 1024 * 1024 * 1024; // 1GB 溢出缓存
tileset.maximumNumberOfLoadedTiles = 500; // 同时加载瓦片数限制
3️⃣ 关闭干扰项
// ❌ 关闭可能导致重载的优化选项
tileset.dynamicScreenSpaceError = false;    // 关闭动态误差
tileset.preloadFlightDestinations = false;  // 关闭飞行预加载
tileset.preloadWhenHidden = false;          // 关闭隐藏预加载
tileset.cullRequestsWhileMoving = false;    // 关闭移动剔除
4️⃣ 强制几何误差(数据修复)
// 🛠️ 如果tileset.json本身有问题,强制覆盖
tileset.geometricError = 4096;  // 设大几何误差
if (tileset.root) {
    tileset.root.geometricError = 1024;
    
    // 递归设置所有子瓦片
    const setGeometricError = (tile) => {
        tile.geometricError = 256;
        tile.children?.forEach(setGeometricError);
    };
    setGeometricError(tileset.root);
}

🎯 问题二:readyPromise 报错

🔍 现象描述

// ❌ 报错代码
const tileset = await Cesium.Cesium3DTileset.fromUrl("/dixing/tileset.json");
tileset.readyPromise.then(() => {  // TypeError: tileset.readyPromise.then is not a function
    console.log("Tileset ready");
});

📅 原因分析:CesiumJS API 重大变更

版本 3D Tiles 加载方式 readyPromise 状态
1.104 之前 new Cesium.Cesium3DTileset(options) ✅ 存在 旧API
1.104 - 1.106 两者兼容 ⚠️ 弃用警告 过渡期
1.107+ Cesium.Cesium3DTileset.fromUrl() 已移除 当前版本

💊 解决方案

✅ 方案A:async/await(推荐)
// ✨ 新版标准写法
try {
    // fromUrl 直接返回Promise,await后即完全就绪
    const tileset = await Cesium.Cesium3DTileset.fromUrl(
        "/dixing/tileset.json",
        {
            maximumScreenSpaceError: 128,
            skipLevelOfDetail: true,
            baseScreenSpaceError: 1024
        }
    );
    
    // 🎉 直接使用,无需readyPromise!
    viewer.scene.primitives.add(tileset);
    viewer.zoomTo(tileset);
    
    // ✅ 属性直接访问
    console.log("包围盒:", tileset.boundingSphere);
    console.log("几何误差:", tileset.geometricError);
    
    // ✅ 样式直接修改
    tileset.brightness = 0.6;
    tileset.contrast = 0.3;
    
} catch (error) {
    console.error("加载失败:", error);
}
✅ 方案B:Promise.then
Cesium.Cesium3DTileset.fromUrl("/dixing/tileset.json")
    .then(tileset => {
        viewer.scene.primitives.add(tileset);
        viewer.zoomTo(tileset);
    })
    .catch(error => console.error("加载失败:", error));

⚠️ 其他相关API变更

1️⃣ 影像服务同样受影响
// ❌ 旧版(1.107之前)
const provider = new Cesium.WebMapTileServiceImageryProvider(options);
provider.readyPromise.then(() => {...});

// ✅ 新版(1.107+)
const provider = await Cesium.WebMapTileServiceImageryProvider.fromUrl(url, options);
viewer.imageryLayers.addImageryProvider(provider);  // 直接使用
2️⃣ 内存配置参数更名(1.110+)
// 📌 旧参数(仍兼容)
tileset.maximumMemoryUsage = 2048;

// ✨ 新参数(推荐)
tileset.cacheBytes = 512 * 1024 * 1024;      // 512MB 缓存
tileset.maximumCacheOverflowBytes = 512 * 1024 * 1024; // 512MB 溢出缓冲

🎨 完整解决方案代码

// =============================================
// 🚀 3D Tiles 大模型优化 + 新版API 完整示例
// =============================================

const init3DTiles = async (viewer, url = "/dixing/tileset.json") => {
    try {
        // 1️⃣ 加载3D Tiles(新版API)
        const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
            // 🎯 大模型优化配置
            maximumScreenSpaceError: 128,
            skipLevelOfDetail: true,
            baseScreenSpaceError: 1024,
            skipScreenSpaceErrorFactor: 32,
            skipLevels: 2,
            immediatelyLoadDesiredLevelOfDetail: false,
            loadSiblings: false,
            
            // 📦 内存配置
            maximumMemoryUsage: 2048,
            // cacheBytes: 512 * 1024 * 1024,  // 1.110+ 推荐
        });

        // 2️⃣ 添加到场景
        viewer.scene.primitives.add(tileset);
        
        // 3️⃣ 样式调整(解决模型太暗)
        tileset.brightness = 0.6;
        tileset.contrast = 0.3;
        tileset.saturation = 0.2;
        tileset.colorBlendMode = Cesium.ColorBlendMode.MIX;
        tileset.colorBlendAmount = 0.5;
        
        // 4️⃣ 强制几何误差(如果数据有问题)
        // tileset.geometricError = 4096;
        
        // 5️⃣ 定位到模型
        await viewer.zoomTo(tileset);
        
        console.log("✅ 3D Tiles加载成功!", {
            包围盒: tileset.boundingSphere,
            几何误差: tileset.geometricError,
            瓦片总数: tileset.totalTiles
        });
        
        return tileset;
        
    } catch (error) {
        console.error("❌ 3D Tiles加载失败:", error);
        throw error;
    }
};

// 使用示例
onMounted(async () => {
    // 初始化viewer...
    const tileset = await init3DTiles(viewer, "/dixing/tileset.json");
});

📊 总结与建议

✅ 问题诊断 Checklist

现象 可能原因 解决方案
🔄 瓦片反复加载卸载 几何误差过小/包围盒异常 调大 maximumScreenSpaceError,开启 skipLevelOfDetail
💥 readyPromise 报错 CesiumJS 1.107+ API变更 改用 fromUrl(),直接await
🌑 模型太暗 默认无光照 调整 brightness/contrast/saturation
🐌 加载缓慢 缓存设置太小 调整 maximumMemoryUsage/cacheBytes

🚦 版本建议

如果你正在新项目中使用 CesiumJS,强烈建议:

  1. 升级到 1.107+:享受新版API的简洁(不再有readyPromise嵌套)
  2. 大模型必须调参:默认配置是为小模型优化的
  3. 数据先行:90%的频繁加载问题源于数据本身,而非代码

📚 参考文档


💭 写在最后

CesiumJS 作为一个快速迭代的开源项目,API 变化在所难免。遇到问题不要慌,先确认版本,再看文档,最后才是调试代码。

希望这篇文章能帮你节省几个小时的调试时间!如果你还有其他 CesiumJS 相关的问题,欢迎在评论区留言讨论~

如果本文对你有帮助,请点赞 👍 收藏 ⭐ 分享 📢


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐