第一次接触CesiumJS的3D Tiles功能时,很多人会卡在"加载成功之后该做什么"这个问题上。确实,把模型显示出来只是第一步,真正有意思的是后续对tileset对象的操作。这个对象就像是你和3D模型之间的遥控器,掌握了它,你就能让模型乖乖听话。
目前主流的加载方式有两种写法,原始文章已经给出了示例。这里我想特别强调几个容易踩坑的地方:首先,从CesiumJS 1.107版本开始,旧的readyPromise写法已经被废弃,必须改用fromUrl或fromIonAssetId这种异步加载方式。其次,加载时一定要记得开启地形深度检测(depthTestAgainstTerrain),否则你的模型可能会"飘"在空中或者"陷"入地下。
加载完成后返回的tileset对象包含了很多实用信息:
理解这些属性的用途很关键。比如boundingSphere不仅包含了模型的位置信息,其radius属性还能帮你计算合适的观察距离。我常用这个值乘以2作为初始视角距离,这样能保证模型完整显示在视野中。
模型加载完成后,第一个需求通常就是自动定位到合适的位置。很多新手会直接用viewer.zoomTo(tileset),这往往会导致视角过近或过远。经过多次实践,我发现结合HeadingPitchRange能获得更好的效果:
javascript复制viewer.zoomTo(
tileset,
new Cesium.HeadingPitchRange(
0.0, // 朝向角度
-0.5, // 俯仰角度
tileset.boundingSphere.radius * 2.0 // 距离
)
)
这个配置中,-0.5的俯仰角度模拟了常见的45度斜视角,而两倍半径的距离确保模型完整显示。对于超大型模型,你可能需要调整这个倍数,我建议通过试错法找到最佳值。
更高级的定位需求是动态追踪。比如在模拟飞行场景中,需要相机始终锁定移动的飞机模型。这时可以用每帧回调实现:
javascript复制viewer.scene.postUpdate.addEventListener(function() {
const position = Cesium.Matrix4.getTranslation(
tileset.root.transform,
new Cesium.Cartesian3()
);
viewer.camera.lookAt(
position,
new Cesium.HeadingPitchRange(0, -0.5, 100)
);
});
当模型复杂度过高时,性能问题就会凸显。通过tileset对象我们可以做多种优化:
LOD控制是最有效的优化手段之一。3D Tiles本身就是为LOD设计的,但我们可以进一步微调:
javascript复制const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
skipLevelOfDetail: true,
baseScreenSpaceError: 1024,
skipScreenSpaceErrorFactor: 16,
skipLevels: 1,
immediatelyLoadDesiredLevelOfDetail: false,
loadSiblings: false,
cullWithChildrenBounds: true
});
这些参数需要根据具体场景调整:
内存管理也很重要。对于动态加载卸载的场景,记得及时清理:
javascript复制// 移除模型
viewer.scene.primitives.remove(tileset);
// 释放内存
tileset.destroy();
我曾遇到一个项目因为忘记destroy导致内存泄漏,最终浏览器标签页崩溃。现在养成了习惯,在移除模型前一定先调用destroy()。
tileset的真正威力在于交互能力。通过屏幕坐标到场景坐标的转换,我们可以实现点击查询:
javascript复制const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function(movement) {
const picked = viewer.scene.pick(movement.endPosition);
if (Cesium.defined(picked) && picked.primitive === tileset) {
const feature = picked.getProperty('属性名');
console.log('选中的要素:', feature);
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
对于属性查询,有几点经验分享:
更复杂的交互如高亮选中要素,可以通过样式来实现:
javascript复制tileset.style = new Cesium.Cesium3DTileStyle({
color: {
conditions: [
['${selected} === true', 'color("red")'],
['true', 'color("white")']
]
}
});
掌握了基础操作后,可以尝试更高级的应用。比如动态修改模型位置:
javascript复制const transform = Cesium.Matrix4.fromTranslation(
new Cesium.Cartesian3(10, 20, 0)
);
Cesium.Matrix4.multiply(
tileset.root.transform,
transform,
tileset.root.transform
);
组合多个tileset时,管理它们的相对位置很重要。我通常创建一个虚拟的"父节点"来统一控制:
javascript复制const container = new Cesium.PrimitiveCollection();
container.add(tileset1);
container.add(tileset2);
viewer.scene.primitives.add(container);
// 整体移动
const transform = Cesium.Matrix4.fromTranslation(
new Cesium.Cartesian3(100, 50, 0)
);
container.modelMatrix = transform;
在处理超大型场景时,我建议采用分块加载策略。通过tileset.initialTilesLoaded事件可以实现按需加载:
javascript复制tileset.initialTilesLoaded.addEventListener(function() {
// 初始块加载完成后,再加载周边区域
loadAdjacentTiles();
});
这些技巧都是在实际项目中踩坑后总结出来的。记得第一次做大规模3D场景时,因为不懂LOD控制,导致浏览器直接卡死。现在回头看,掌握tileset对象的各种方法和属性,就像是获得了操控3D世界的魔法钥匙。