sketch up模型在Cesium中展示

2,538 阅读3分钟

背景

前段时间收到个调研需求,客户希望可以将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插件

image.png

image.png

使用插件发布文件到Cesium ion上(第一次使用需要Cesium ion登陆授权)

image.png

上传完成后,Cesium ion会自动转换为3d tiles格式文件

image.png

如果需要添加坐标信息,点击模型预览上的文字,选择对应的地形。

image.png

image.png

然后在这边可以搜索地区或输入坐标,还能修改模型的一些基本预览信息。

image.png

保存修改后直接返回下载3d tiles文件压缩包。

image.png

最后只要在Cesium中加载3d tiles就好了。

image.png

核心代码如下,此处使用了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)。

再唠唠

离上一次发文章也有一段时间了,今年的目标就是输出尽可能多有用的文章吧,不要再摆烂了!