Cesium 入门
cesium, 读作[ˈsiːziəm],中文直译是化学元素铯
cesium 是一个用于显示三维地球影像的javscript开源库,它基于webgl,旨在释放3d渲染的力量。能够在web平台搭建虚拟地球以及场景展示应用。
webgl: 一个底层绘图javasctipt API集合。threejs是一个知名的webgl框架。threejs和webgl的关系类似于jquery和原生js的关系。它是一个工具库。
cesium没有采用threejs类似的工具库,是直接基于webgl来开发, cesium是对于webgl具体的应用(绘制虚拟地球),而我们在cesium搭建的虚拟地球中扩展出更为具体的应用。
接下来会介绍一个具体的例子,在东方明珠塔上方放置一个飞向兰州的飞机,然后通过这个例子穿插一些知识的讲解。
http://localhost:8080/Apps/Sandcastle/index.html?src=air.html
准备工作
- npm install cesium
- 引入一个cdn打包好的js脚本文件
注意一下使用的时候需要在cesium官网上注册一个token, 因为你获取到的数据都是通过cesium.ion 这个云平台上获取的。不准确的说,cesium和cesium ion的关系类似于git和github, 或者docker和dockerhub的关系。
或者也不需要进行前面的步骤,可以直接将github上将cesium源码clone下来,启动示例服务。cesium有很多例子并且实时修改源码看效果,是一个很好的学习方式。
把代码拷下来后运行:
npm install
// 安装依赖
npm run build
// 构建cesium源码
npm run build-docs
// 构建cesium文档
npm run start
// 启动示例服务
第一步:来绘制一个地球
const viewer = new Cesium.Viewer("cesiumContainer");
http://localhost:8080/Apps/Sandcastle/index.html
在js中一行代码即可。可见使用cesium非常的简单,但这里有几个概念需要讲解一下。
cesium类似于vue,react等一样,需要一个挂载点,即你需要把你的画面渲染在html中的哪一个dom节点。这个cesiumContainer即挂载点id。
不过你发现,其实这样写可以:
const viewer = new Cesium.CesiumWidget("cesiumContainer");
http://localhost:8080/Apps/Sandcastle/index.html?src=Cesium%20Widget.html
少了哪些东西?少了界面的很多控件,那些都是dom,暂时先忽略。而cesium真正创建三维窗口,渲染页面到canvas上的,是cesiumWidget这个核心类。
cesiumWidget的主要结构(它的属性)
- clock主要用来记录时间,因为cesium渲染出的地球是动态展示的,所以需要通过时间来绘制某一帧的内容。
- container即刚刚讲到的挂载点,通过它可以拿到dom对象。
- canvas则是在container上构建的Canvas类对象,通过webgl渲染的场景都绘制在这个canvas上。
- screenSpaceEventHandler, 可以通过这个对象来监听一些屏幕上的事件,比如鼠标移动,鼠标点击等等。
注意哈,这些属性都是小写的,它们的引用都是实例。
scene对象
scene中装载了所有的图元对象,比较重要。图元对象就是cesium用来绘制对象的最小单位。在默认的实例中,都有哪些图元对象呢?显然有地球(globe),skyBox(天空盒,即后面的宇宙背景),sun(太阳),moon(月亮,不过我没找到。。),这些都是系统内置的。
另外还有两个用来由用户自行添加的图元对象的容器:primitives和groundPrimtives数组。
我们后续添加飞机模型的时候,就是将创建好的飞机模型,添加进primitives。
常见的图元类
上述的图元对象都由图元类进行实例化,常见的图元类有Globe,Model,Primitive,Billboards,Labels等
Globe地球类
通过它来实例化出地球。他需要两个东西,一个是地形高程信息,另外一个是影像图层。
影像图层,即拍摄出的很多很多地球图片。地形高程信息,即地球的每个地点的海拔高度数据。这两个数据其实都是通过云平台来实时获取的(默认是通过cesium.ion的, 但其实可以设置provider(提供者), 比如也可以通过google earth,NASA来获取地球影像信息或者地形高程信息)。注意哈,这两个数据量巨大,不可能一次性加载到位,它是懒加载的,即通过视角的位置来获取相应经纬度坐标的数据,可以通过控制台来看到这些数据,每次视角移动时,都会加载很多张图片:
所以绘制出地球影像,是一个个图片切片拼接上去的。
影像可以进行叠加,就像下面这个例子中,添加了两个影像图层:外层的图层设置了透明度。
http://localhost:8080/Apps/Sandcastle/index.html?src=image-cutout.html
初始情况下,地形高程信息默认为0,即渲染地球的时候是没有高度数据的。但可以通过设置terrainProvider来设置地形信息的提供方,这样绘制出的地球会更加生动(因为有了高度数据)
http://localhost:8080/Apps/Sandcastle/index.html?src=Terrain.html&label=Development
Model模型类
通过它来解析引入的3d模型数据,常见的3d模型数据格式是gltf, 由一些常见的3d建模工具生成,比如3ds Max, Maya等。 gltf其实是一个json。一个json怎么描述一个模型呢?主要就是通过顶点和三角形数据。其中模型中的每一个顶点都是在一个空间直角坐标系中的一个点,再把其中相应的顶点连接起来,形成一个个三角形,就可以描述一个模型数据。
Primitive几何类
通过这个类来创建一些简单的几何体,比如立方体椭球体等等
http://localhost:8080/Apps/Sandcastle/index.html?src=development%2FBox.html&label=Development
Billboards/Labels
Billborads可以理解成html5的img
http://localhost:8080/Apps/Sandcastle/index.html?src=development%2FBillboards.html&label=Development
Labels可以理解成文字元素
http://localhost:8080/Apps/Sandcastle/index.html?src=development%2FLabels.html&label=Development
特点:
- 这两个类创建出的图元都是始终面朝屏幕(没有贴着地形走)
- 都是批量创建的,不能在场景中添加单独的Billboard图元(但系统里确实有这个类),都是添加的是Billborads(带了s的)
const billboards = scene.primitives.add(
new Cesium.BillboardCollection()
);
billboards.add({
image: "../images/Cesium_Logo_overlay.png", // default: undefined
show: true, // default
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // default
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // default: CENTER
scale: 2.0, // default: 1.0
color: Cesium.Color.LIME, // default: WHITE
rotation: Cesium.Math.PI_OVER_FOUR, // default: 0.0
width: 100, // default: undefined
height: 25, // default: undefined
});
第二步,将飞机放入到场景中
前面带大家简单了解了一些cesiumWidget的整体结构,并且了解了常见的图元类型。现在来解决最开始提出的问题,怎么将飞机模型放入到东方明珠塔大楼上方?
那么这个过程需要经历哪些步骤呢?
1.定位东方明珠塔的位置
第一步比较简单,需要定位东方明珠塔的位置,从网上找到东方明珠塔的经纬度坐标(31.23958,121.499763)
2.根据经纬度坐标在cesium中找到这个位置
首先需要知道cesium定位位置时采用什么坐标系?
它采用的是wgs84坐标系,它是一个空间直角坐标系,原点定义为地球的质心,x轴指向赤道平面和本初子午线的交点,它是一个右手系,进而再确定出y轴指向。
所以,在代码中将经纬度坐标转换成wgs84坐标
const height = 5000
const origin = Cesium.Cartesian3.fromDegrees(
121.499763,
31.23958,
height // 距离地面高度为5000m
);
// 可以打印出这个坐标,显然,这个单位是米
console.log(origin) // (-1705102.1665893272, 4991709.421547006, 3582384.484603829)
3.设置飞机的朝向
前面我已经找到了这个位置,但是我要怎么设置模型的朝向呢?这一步相对麻烦一点
想象一下,飞机一共可以往三个方向旋转,如图:
那么,怎么设置这三个角度呢?
同样,还是需要引入一个坐标系,这个坐标系是已飞机模型的中心(这个中心是建模软件设置的)为原点,已当地的正东方向为x轴,正北方向为y轴,z轴指向宇宙,垂直于地面。这个坐标轴同样是右手系,注意在cesium里面的坐标系都是右手系。
如图:
有了这三个轴,便可以设置相应的角度来设置模型的朝向:
分别是:
- yaw是围绕z轴旋转,叫做偏航角(在cesium中叫做heading)
- pitch是围绕y轴旋转,叫做俯仰角
- roll 围绕x轴旋转,也叫做翻滚角
在cesium中代码如下:
const heading = -156 // 顺时针方向转动为正,往逆时针转动156°大约是飞机朝向兰州的方向
const pitch = 0 // 不设置俯仰角, 默认是平的
const roll = 0 // 不设置翻滚角, 默认是平的
const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(
origin,
hpr
);
// headingPitchRollToFixedFrame是将模型位置,朝向整合到一起放入到一个4*4的矩阵中
// 然后从gltf文件中加载模型数据
model = scene.primitives.add(
await Cesium.Model.fromGltfAsync({
url: '../../SampleData/models/CesiumAir/Cesium_Air.glb',
modelMatrix: modelMatrix
})
);
为什么是4*4的矩阵?按道理来说这里的空间是3维数据,但是为什么这里是4*4的矩阵呢?
是为了方便计算。对模型做变换时,比如说对模型做缩放或者旋转变换时,实际都是可以表示成矩阵之间的乘法,但是3*3下的数据是无法用矩阵乘法表示平移的,但是当转换成齐次坐标系时(4*4),则统一都可以表示成矩阵乘法。
4.设置相机的朝向
走到这里,已经设置好了飞机的位置以及朝向。
或者更具体的说,我们设置了一个场景,这个场景中有宇宙背景,有太阳,有月亮,还有地球(这些其实没有写任何代码,是cesium自动添加上去的),当然还有一个飞机(这是我们手动添加的)。
但这里还有个问题,就是你需要设置一个眼睛(专业说法叫做camera,相机),或者说你需要定义你怎么去看这个场景。我可以去看刚刚添加的飞机模型,当然也可以去看埃及金字塔。我可以仰视飞机,当然也可以俯视飞机。
其实设定相机的位置很好理解,跟前面设定飞机模型位置的过程一摸一样。
但怎么设定相机的朝向呢,不难发现,其实也是在相机上定义旋转轴,同样也是有3个角度进行设置
不过区别在于,这里的x轴是指向正北方向的,也就是说初始相机是看向北边的。
来看一下代码:
const camera = viewer.camera; // 拿到相机
// Zoom to model
const heading = Cesium.Math.toRadians(0); //设定偏航角
const pitch = Cesium.Math.toRadians(0); // 设定俯仰角
const range = 200
// 注意,这里的center设置的是相机的焦点,我设置为飞机模型的位置,所以就体现成,镜头看向了飞机。
camera.lookAt(
origin,
new Cesium.HeadingPitchRange(heading, pitch, range)
// 这个api就是设置刚刚的3个角度,不过这个api没有暴露出roll,因为这个设置不是很常用,但同样也可以其他api进行设置(setView)。取而代之的是距离焦点的距离,我设置距离这个飞机为200m。
);
总结
本文一开始介绍了cesium的安装使用,然后介绍了cesiumWidget这个真正渲染出画面的这个核心类,以及cesiumWidget挂载的一些属性,其中比较重要的是scene,scene描述了场景中所有的图元对象,比如地球,飞机,太阳等等(虽然地球要比飞机要复杂很多,但是它们都是图元对象,是平行的,并不存在上下级关系,而且图元类是没有基类的。)
然后则是对把飞机放入场景的这个过程进行了描述,主要是设置了飞机的位置以及朝向,然后设置了相机的位置以及方向。