背景
前段时间收到个调研需求,客户希望可以将sketch up模型(后面统称skp模型)在Cesium中展示,以便更快的预览到项目落地效果。目前Cesium仅支持加载3d tiles和部分3d model格式文件。但由于客户提供的skp模型文件文件普遍较大,直接加载3d model文件速度慢也容易导致页面卡死,所以最终决定调研skp模型转为3d tiles的方法。
目前的几种解决方案
| 方案 | 优点 | 缺点 |
|---|---|---|
| 开源工具(obj23dtiles, obj2gltf) | 免费,可以通过代码批量转换 | 转换步骤繁琐,需要从 skp -> obj -> gltf -> 3dtiles,兼容性不稳定 |
| CesiumLab | 支持的转换格式多 | 不支持mac,不知道为什么win注册了登陆不了 |
| Cesium ion | 个人版免费,官方工具,支持转换格式多,操作简单,SketchUp软件上有支持插件 | 部分skp模型转换会失败报错,不能单次批量转换 |
经具体使用后,最终选择方案3。
具体流程
在SketchUp中安装Cesium ion插件
使用插件发布文件到Cesium ion上(第一次使用需要Cesium ion登陆授权)
上传完成后,Cesium ion会自动转换为3d tiles格式文件
如果需要添加坐标信息,点击模型预览上的文字,选择对应的地形。
然后在这边可以搜索地区或输入坐标,还能修改模型的一些基本预览信息。
保存修改后直接返回下载3d tiles文件压缩包。
最后只要在Cesium中加载3d tiles就好了。
核心代码如下,此处使用了Resium来实现。
import { useCallback, useMemo, useRef } from 'react';
import { Cartesian3, Transforms, Viewer as CesiumViewer, Cesium3DTileset } from "cesium";
import { Viewer, Cesium3DTileset as Resium3DTileset, CesiumComponentRef } from "resium";
function App()
{
const ref = useRef<CesiumComponentRef<CesiumViewer>>(null);
const onAllTilesLoad = useCallback(() =>
{
console.log("onAllTilesLoad")
}, [])
const onInitialTilesLoad = useCallback(() =>
{
console.log("onInitialTilesLoad")
}, [])
const onTileFailed = useCallback((error: any) =>
{
console.log("onTileFailed", error)
}, [])
const onTileLoad = useCallback((tile: Cesium3DTileset) =>
{
console.log("onTileLoad", tile)
}, [])
const onTileUnload = useCallback(() =>
{
console.log("onTileUnload")
}, [])
const modelMatrix = useMemo(() =>
{
return Transforms.eastNorthUpToFixedFrame(
Cartesian3.fromDegrees(0, 0, 100)
)
}, [])
return (
<Viewer
full
ref={ref}
geocoder={false}
homeButton={false}
sceneModePicker={false}
navigationHelpButton={false}
baseLayerPicker={false}
animation={false}
timeline={false}
fullscreenButton={false}
>
<Resium3DTileset
url="http://127.0.0.1:3004/tileset.json"
onAllTilesLoad={onAllTilesLoad}
onInitialTilesLoad={onInitialTilesLoad}
onTileFailed={onTileFailed}
onTileLoad={onTileLoad}
onTileUnload={onTileUnload}
onReady={tileset =>
{
ref.current?.cesiumElement?.zoomTo(tileset)
}}
modelMatrix={modelMatrix}
/>
</Viewer>
);
}
export default App;
如果想要加载的模型找一个参照点以便做大小、角度、位置的矩阵变换来贴合底图,实现代码如下(直接拿大佬的代码)
window.CESIUM_BASE_URL = '/'
import * as Cesium from 'cesium'
import "../node_modules/cesium/Build/Cesium/Widgets/widgets.css"
Cesium.Ion.defaultAccessToken = 'xxxxxx'
const viewer = new Cesium.Viewer('cesiumContainer')
var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
url: 'http://127.0.0.1:5500/src/tiles/tileset.json',
maximumScreenSpaceError: 2,
}));
(async () => {
try {
//x轴平移量
const tx=0;
//y轴平移量
const ty=0;
//z轴平移量
const tz=-0;
//x轴旋转角度
const rx =0;
//y轴旋转角度
const ry =0;
//z轴旋转角度
const rz=0;
//缩放比
const scale=1;
tileset.readyPromise.then((tileset) => {
const pos = {
//经度
lng: 113.1731120785,
//纬度
lat: 22.1027207644,
//高度
alt: -20
};
//变换的参考点
const surface = Cesium.Cartesian3.fromDegrees(pos.lng, pos.lat, pos.alt);
const m = Cesium.Transforms.eastNorthUpToFixedFrame(surface);
//平移
const _tx=tx?tx:0;
const _ty=ty?ty:0;
const _tz=tz?tz:0;
const tempTranslation = new Cesium.Cartesian3(_tx, _ty, _tz);
const offset =Cesium.Matrix4.multiplyByPoint(m, tempTranslation, new Cesium.Cartesian3(0,0,0));
const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
//旋转及缩放
if(rx){
const mx = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(rx));
const rotate= Cesium.Matrix4.fromRotationTranslation(mx);
Cesium.Matrix4.multiply(m, rotate, m);
}
if(ry){
const my = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(ry));
const rotate= Cesium.Matrix4.fromRotationTranslation(my);
Cesium.Matrix4.multiply(m, rotate, m);
}
if(rz){
const mz = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(rz));
const rotate= Cesium.Matrix4.fromRotationTranslation(mz);
Cesium.Matrix4.multiply(m, rotate, m);
}
if(scale){
const _scale = Cesium.Matrix4.fromUniformScale(scale);
Cesium.Matrix4.multiply(m, _scale, m);
}
tileset._root.transform = m;
//参照点
const entity = {
id:"test",
point: new Cesium.PointGraphics({
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
color: Cesium.Color.fromCssColorString("#ff0033"),
pixelSize: 30,
outlineColor:Cesium.Color.fromCssColorString("#ff0033"),
outlineWidth: 1,
}),
position:surface
};
viewer.entities.add(entity);
});
await tileset.readyPromise;
await viewer.zoomTo(tileset);
viewer.flyTo(tileset);
const extras = tileset.asset.extras;
if (
Cesium.defined(extras) &&
Cesium.defined(extras.ion) &&
Cesium.defined(extras.ion.defaultStyle)
) {
tileset.style = new Cesium.Cesium3DTileStyle(extras.ion.defaultStyle);
}
} catch (error) {
console.log(error);
}
})();
总结
至此,整个调研过程就结束了,最后个人感觉官方的东西还是得用官方的工具来解决呀(doge)。
再唠唠
离上一次发文章也有一段时间了,今年的目标就是输出尽可能多有用的文章吧,不要再摆烂了!