本文为稀土掘金技术社区首发签约文章,14 天内禁止转载,14 天后未获授权禁止转载,侵权必究!
哈喽,大家好 我是
xy
👨🏻💻。这篇文章是Cesium 三维地球可视化从入门到进阶
专栏的第五篇。主要是对Cesium
中创建图形常用的两种方法:Entity
、Primitive
做详解。
截至目前,
Cesium
基础知识基本上都讲解的差不多了,如果你对基础的一些使用方式还是不了解的话,可以参考前面几节内容。
- 🎯三维地球可视化从入门到进阶 - 基础详解
- 🎯三维地球可视化从入门到进阶 - Viewer、Scene、Camera、加载第三方影像、地形服务
- 🎯三维地球可视化从入门到进阶 - 坐标系统
- 🎯三维地球可视化从入门到进阶 - 鼠标键盘事件、相机事件、数据加载事件、场景加载事件
- 🎯三维地球可视化从入门到进阶 - 实体 Entity、图形 Primitive 详解
后续的文章将会以分享案例的形式分享,如果你也喜欢 三维地球可视化
, 记得一定要关注我。
Entity
实体简介
实体(entity
)是 Cesium
中自带的创建图形的方法,通过该方法可以在场景中创建点
、线
、面
、多边形
、立方体
、圆等
基本图形。
截止目前为止,Cesium
中支持 17
种实体:
实体名称 | 类型 | 描述 | 图例 |
---|---|---|---|
billboard | BillboardGraphics | 广告牌,可以自定义为图片或文字 | |
box | BoxGraphics | 盒子 | |
corridor | CorridorGraphics | corridor 直译为走廊,实际上是将多个点相连形成的一种实体。 | |
cylinder | CylinderGraphics | 圆柱体,可以通过设置上下半径生成锥体 | |
ellipse | EllipseGraphics | 椭圆 | |
ellipsoid | EllipsoidGraphics | 椭球体 | |
label | LabelGraphics | 标签 | |
model | ModelGraphics | 模型,主要用于加载 glTF 模型 | |
tileset | Cesium3DTilesetGraphics | 瓦片集合,主要用于加载 3D tiles 模型 | |
path | PathGraphics | 路径,实际上是将大量的点连起来绘制的一种线条实体 | |
plane | PlaneGraphics | 平面 | |
point | PointGraphics | 点 | |
polygon | PolygonGraphics | 多边形 | |
polyline | PolylineGraphics | 折线 | |
polylineVolume | PolylineVolumeGraphics | 折线体 | |
rectangle | RectangleGraphics | 矩形 | |
wall | WallGraphics | 墙 |
简单示例
下面以创建一个盒子为例,介绍创建一个实体的基本方法:
var boxEntity = viewer.entities.add({
id: '1234abcdxxxx', // 实体的ID,具有唯一性
name: 'Name of the entity', // 实体的名称,可以不唯一
position: Cesium.Cartesian3.fromDegrees(-107.0, 40.0, 300000.0), // 实体的坐标
box: {
dimensions: new Cesium.Cartesian3(400000.0, 300000.0, 500000.0),
material: Cesium.Color.RED.withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.BLACK
}
});
viewer.zoomTo(boxEntity) // 视角定位到实体
其效果如下:
创建一个实体一般包含以下几个基本属性:
id
(非必须):标识该实体的 ID,具有唯一性;name
(非必须):标识该实体的名称,可以不唯一;position
(必须):指定该实体存在的位置;- 实体的类型及其属性(必须):如上面例子中的
box
,不同的实体表达的图形不一样,其所需属性也不同,下面会依次介绍不同实体的创建方法。
其中id
和name
属性即使不指定,也会成功创建实体并且不会报错,但是若缺少位置信息,程序虽不会报错,但是无法成功创建该实体。
盒子 box
下面是创建一个盒子的完整代码:
var boxEntity = viewer.entities.add({
id: '123123123', // 盒子的ID
name: 'Name of the entity', // 盒子的名称
position: Cesium.Cartesian3.fromDegrees(-107.0, 40.0, 300000.0), // 盒子的位置
box: {
dimensions: new Cesium.Cartesian3(400000.0, 300000.0, 500000.0), // 尺寸
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 2000000), // 可视距离
material: Cesium.Color.RED.withAlpha(0.5), // 材质
fill: true, // 材质填充
outline: true, // 显示轮廓线
outlineColor: Cesium.Color.BLACK, // 轮廓线颜色
outlineWidth: 1.0, // 轮廓线宽度
show: true, // 显示盒子
}
});
各个属性的释义如下:
尺寸:dimensions
dimensions
属性所需的参数为一个笛卡尔空间直角坐标系的对象,Cartesian3(x, y, z)
其中的三个参数分别代表笛卡尔空间直角坐标系的的x
、y
、z
,可以简单理解为设置盒子的长宽高,如下图:
可视距离:distanceDisplayCondition
distanceDisplayCondition
表示实体的可视距离,所需的参数为一个DistanceDisplayCondition
对象,DistanceDisplayCondition(near, far)
的两个参数表达该实体的可视距离,near
表示可视的最近距离,far
表示可视的最远距离,如下面伪代码,表示在 0 到 2000000 米的范围内该实体可见:
box: {
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 2000000), // 可视距离为0到2000000米之间
}
效果如图,该实体随着镜头的拉伸而显示和隐藏:
材质:material
material
属性用于设置盒子表面的材质,默认值为白色,在上面的例子中将盒子的材质设置为了 0.5 透明度的红色材质:
box: {
material: Cesium.Color.RED.withAlpha(0.5), // 材质为半透明的红色
}
材质填充:fill
fill
属性的参数为布尔值,用于控制盒子的面是否用材质填充,默认值为true
,所以在图中可见盒子的面都是半透明红色的,若将其置为false
:
box: {
fill: flase, // 取消材质填充
}
其效果图如下,只剩下盒子轮廓线:
轮廓线:outline
outline
属性的参数为布尔值,用于控制盒子是否显示轮廓线,默认值为true
,当把outline
的值置为false
时,则会隐藏盒子的轮廓线:
box: {
outline: flase, // 隐藏盒子轮廓线
}
其效果图如下:
轮廓线颜色:outlineColor
outlineColor
属性的参数为 Cesium 颜色值,其默认值为黑色,可以修改为其他颜色:
box: {
outlineColor: Cesium.Color.PINK, // 将盒子轮廓线颜色修改为粉色
}
轮廓线宽度:outlineWidth
outlineWidth
属性的参数为数值类型,用于控制轮廓线的宽度,默认值为 1.0,可以将其线条宽度加粗:
box: {
outlineWidth: 10, // 将盒子轮廓线宽度加粗
}
显示和隐藏:show
show
属性的参数为布尔值,用于控制盒子的显示和隐藏,默认值为true
,将其置为false
后盒子就隐藏了:
box: {
show: false, // 将盒子隐藏
}
点 point
下面是创建一个点的完整代码:
var pointEntity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-107.0, 40.0, 0.0), // 点的位置
point: {
color: Cesium.Color.RED, // 颜色
outlineColor: Cesium.Color.PINK, // 轮廓线颜色
outlineWidth: 5, // 轮廓线宽度
pixelSize: 20, // 点的大小
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(10, 10000), // 可视距离
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 相对高度
scaleByDistance: new Cesium.NearFarScalar(100, 1, 10000, 15), // 距离缩放
translucencyByDistance: new Cesium.NearFarScalar(100, 0.4, 200, 0.8), // 距离透明
show: true, // 显示和隐藏
}
})
效果图如下:
各个属性的释义如下:
颜色:color
color
属性的参数为 Cesium 颜色值,其默认值为白色,可以修改为其他颜色:
point: {
color: Cesium.Color.BLACK, // 将点的颜色置为黑色
}
轮廓线颜色:outlineColor
outlineColor
属性的参数为 Cesium 颜色值,其默认值为黑色,可以修改为其他颜色:
point: {
outlineColor: Cesium.Color.PINK, // 将点的颜色置为粉色
}
轮廓线宽度:outlineWidth
outlineWidth
属性的参数为 Cesium 颜色值,其默认值为 0,单位为像素:
point: {
outlineWidth: 10, // 将点的轮廓线宽度置为10
}
点的大小:pixelSize
pixelSize
用于控制点的大小,单位为像素,其默认值为 1:
point: {
pixelSize: 20, // 将点的像素大小置为20
}
可视距离:distanceDisplayCondition
distanceDisplayCondition
表示实体的可视距离,所需的参数为一个DistanceDisplayCondition
对象,DistanceDisplayCondition(near, far)
的两个参数表达该实体的可视距离,near
表示可视的最近距离,far
表示可视的最远距离,如下面伪代码,表示在 0 到 2000000 米的范围内该实体可见:
point: {
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 2000000), // 可视距离为0到2000000米之间
}
相对高度:heightReference
heightReference
指的是实体距离地形的相对高度,它有三个可选的值:
可选值 | 值的类型 | 描述 |
---|---|---|
Cesium.HeightReference.NONE | Number | 绝对高度,即在position 中设置的高度就是最后的显示高度 |
Cesium.HeightReference.CLAMP_TO_GROUND | Number | 浮于地表,无论在position 中设置怎么样的高度值,点的位置始终浮于地表,即固定在地形上 |
Cesium.HeightReference.RELATIVE_TO_GROUND | Number | 相对于地形的高度,即在position 中设置的高度值是在地形高度的基础上进行计算的 |
距离缩放:scaleByDistance
scaleByDistance
属性用于指定点在不同的可视距离下放大或缩小的倍数,如下面代码:
point: {
scaleByDistance: new Cesium.NearFarScalar(100, 2, 10000, 15), // 100米以下放大2倍,10000米以上放大15倍
}
NearFarScalar(near, nearValue, far, farValue)
对象接收四个参数:
参数值 | 值的类型 | 默认值 | 描述 |
---|---|---|---|
near | Number | 0.0 | 最近距离 |
nearValue | Number | 0.0 | 小于最近距离时,放大或缩小的倍数 |
far | Number | 1.0 | 最远距离 |
farValue | Number | 0.0 | 大于最远距离时,放大或缩小的倍数 |
其效果图如下,随着距离的变化,到临界值时其点的大小也发生改变:
距离透明:translucencyByDistance
scaleByDistance
属性用于指定点在不同的可视距离下的透明度,如下面代码:
point: {
translucencyByDistance: new Cesium.NearFarScalar(100, 0.2, 10000, 0.8), // 100米以下透明度为0.2,10000米以上透明度为0.8
}
参数依旧是NearFarScalar
不过参数的意义稍有区别:
参数值 | 值的类型 | 默认值 | 描述 |
---|---|---|---|
near | Number | 0.0 | 最近距离 |
nearValue | Number | 0.0 | 小于最近距离时的透明度 |
far | Number | 1.0 | 最远距离 |
farValue | Number | 0.0 | 大于最远距离时的透明度 |
其效果图如下,随着距离的变化,到临界值时其点的透明度也发生变化:
Primitive
图形简介
图形(Primitive
)是 Cesium
中更加高阶
的创建图形的方法,那么相对低阶的方法就是使用实体(Entity)定义一个图形。当创建一个图形时,两者的流程都是定义实体的尺寸大小和定义实体的材质外观。图形(Primitive)由两部分组成:
几何形状
(Geometry):定义了 Primitive 的结构,例如三角形、线条、点等;外观
(Appearance ):定义 Primitive 的着色(Sharding),包括 GLSL(OpenGL 着色语言,OpenGL Shading Language)顶点着色器和片段着色器( vertex and fragment shaders),以及渲染状态(render state)。
图形(Primitive)的优劣
但图形(Primitive)相较于实体(Entity)有如下优势:
性能
:绘制大量 Primitive 时,可以将其合并为单个 Geometry 以减轻 CPU 负担、更好的使用 GPU。合并 Primitive 由 web worker 线程执行,UI 保持响应性;灵活性
:Geometry 与 Appearance 解耦,两者可以分别进行修改;低级别访问
:易于编写 GLSL 顶点、片段着色器、使用自定义的渲染状态 。
同时,图形(Primitive)有如下劣势:
- 需要编写更多地代码;
- 需要对图形编程有更多的理解,特别是 OpenGL 的知识。
Cesium 中支持的图形
图形名称 | 类型 | 描述 |
---|---|---|
BoxGeometry BoxOutlineGeometry | BoxGeometry BoxOutlineGeometry | 盒子 仅有轮廓线的盒子 |
CircleGeometry CircleOutlineGeometry | CircleGeometry CircleOutlineGeometry | 圆形 仅有轮廓线的圆形 |
CoplanarPolygonGeometry CoplanarPolygonOutlineGeometry | CoplanarPolygonGeometry CoplanarPolygonOutlineGeometry | 共面多边形 仅有轮廓线的共面多边形 |
CorridorGeometry CorridorOutlineGeometry | CorridorGeometry CorridorOutlineGeometry | 走廊 仅有轮廓线的走廊 |
CylinderGeometry CylinderOutlineGeometry | CylinderGeometry CylinderOutlineGeometry | 圆柱 仅有轮廓线的圆柱 |
EllipseGeometry EllipseOutlineGeometry | EllipseGeometry EllipseOutlineGeometry | 椭圆 仅有轮廓线的椭圆 |
EllipsoidGeometry EllipsoidOutlineGeometry | EllipsoidGeometry EllipsoidOutlineGeometry | 椭球体 仅有轮廓线的椭球体 |
FrustumGeometry FrustumOutlineGeometry | FrustumGeometry FrustumOutlineGeometry | 截锥体 仅有轮廓线的截锥体 |
GroundPolylineGeometry | GroundPolylineGeometry | 地面多段线 |
PlaneGeometry PlaneOutlineGeometry | PlaneGeometry PlaneOutlineGeometry | 平面 仅有轮廓线的平面 |
PolygonGeometry PolygonOutlineGeometry | PolygonGeometry PolygonOutlineGeometry | 折线 仅有轮廓线的折线 |
PolylineVolumeGeometry PolylineVolumeOutlineGeometry | PolylineVolumeGeometry PolylineVolumeOutlineGeometry | 折线体 仅有轮廓线的折线体 |
RectangleGeometry RectangleOutlineGeometry | RectangleGeometry RectangleOutlineGeometry | 矩形 仅有轮廓线的矩形 |
SimplePolylineGeometry | SimplePolylineGeometry | 简单折线 |
SphereGeometry SphereOutlineGeometry | SphereGeometry SphereOutlineGeometry | 球体 仅有轮廓线的球体 |
WallGeometry WallOutlineGeometry | WallGeometry WallOutlineGeometry | 墙 仅有轮廓线的墙 |
简单示例
在地球上绘制出青色背景上有黄色点的矩形图形:
简单的创建方法
viewer.scene.primitives.add(new Cesium.RectanglePrimitive({
rectangle : Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0), // 绘制矩形
material : Cesium.Material.fromType('Dot') // 设置材质
}))
使用几何形状和外观的创建方法
// GeometryInstance是Geometry的一个容器
const instance = new Cesium.GeometryInstance({
geometry: new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0),
vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
})
})
// 使用抽象的Primitive而不是RectanglePrimitive
const rectanglePrimitive = viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instance,
// 使用该外观,可以使矩形覆盖在地球表面,或者悬浮一定的高度
appearance: new Cesium.EllipsoidSurfaceAppearance({
material: Cesium.Material.fromType('Dot')
})
}))
合并集合图形
合并多个GeometryInstances
为一个Primitive
可以极大的提高性能,下面的例子创建了 2592 一颜色各异的矩形,覆盖整个地球:
创建代码如下:
let instances = [] // 用于存放所有geometry
for (let lon = -180.0; lon < 180.0; lon += 5.0) {
for (let lat = -90.0; lat < 90.0; lat += 5.0) {
instances.push(new Cesium.GeometryInstance({
geometry: new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromDegrees(lon, lat, lon + 5.0, lat + 5.0)
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({
alpha: 0.5
}))
}
}));
}
}
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instances, // 合并
// 某些外观允许每个几何图形实例分别指定某个属性,例如:
appearance: new Cesium.PerInstanceColorAppearance()
}))
图形拾取
即使多个GeometryInstance
被合并为单个Primitive
,仍然可以单独访问。我们可以为每一个GeometryInstance
指定一个id
,并且可以通过scene.pick
来判断该实例是否被选取:
let instance = new Cesium.GeometryInstance({
geometry: new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromDegrees(-100.0, 30.0, -90.0, 40.0)
}),
id: 'rectangle-1',
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
}
})
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.PerInstanceColorAppearance()
}))
let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
// 设置单击事件的处理句柄
handler.setInputAction(function (movement) {
let pick = viewer.scene.pick(movement.position)
if (Cesium.defined(pick) && (pick.id === 'rectangle-1')) {
alert('矩形被选取');
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
更新单个 Instance 的属性
在添加到Primitive
中以后,让然可以修改几何图形的某些属性:
- 颜色:如果
Primitive
设置了PerInstanceColorAppearance
外观,则可以修改ColorGeometryInstanceAttribute
类型的颜色; - 可见性:任何实例可以修改可见性。
代码如下:
let circleInstance = new Cesium.GeometryInstance({
geometry: new Cesium.CircleGeometry({
center: Cesium.Cartesian3.fromDegrees(-95.0, 43.0),
radius: 250000.0,
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(1.0, 0.0, 0.0, 0.5)),
show: new Cesium.ShowGeometryInstanceAttribute(true) // 显示或者隐藏
},
id: 'circle'
})
let primitive = new Cesium.Primitive({
geometryInstances: circleInstance,
appearance: new Cesium.PerInstanceColorAppearance({
translucent: false,
closed: true
})
})
viewer.scene.primitives.add(primitive)
// 定期修改颜色
setInterval(function () {
let attributes = primitive.getGeometryInstanceAttributes('circle') // 获取某个实例的属性集
attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.fromRandom({
alpha: 1.0
}))
}, 1000)
外观(Appearances)
Primitive
由两个重要部分组成:几何图形实例、外观,一个Primitive
只能有一个外观,而可以有多个实例。几何图形定义了结构,外观定义了每个像素被如何着色,外观可能使用材质(Material)。Cesium 支持下表列出的外观:
外观名称 | 类型 | 描述 |
---|---|---|
MaterialAppearance | MaterialAppearance | 支持各种 Geometry 类型的外观,支持使用材质来定义着色 |
EllipsoidSurfaceAppearance | EllipsoidSurfaceAppearance | MaterialAppearance 的一个版本。假设几何图形与地表是平行的,并且依此来进行顶点属性(vertex attributes)的计算 |
PerInstanceColorAppearance | PerInstanceColorAppearance | 让每个实例使用自定义的颜色来着色 |
PolylineMaterialAppearance | PolylineMaterialAppearance | 支持使用材质来着色多段线 |
PolylineColorAppearance | PolylineColorAppearance | 使用每顶点或者每片段(per-vertex or per-segment )的颜色来着色多段线 |
外观定义了需要在 GPU 上执行的完整的 GLSL 顶点、片段着色器,通常不需要修改这一部分,除非需要定义自己的外观。外观还定义了完整的render state
,用于在绘制Primitive
时控制 GPU 的状态,可以直接或者通过高层 API 来定义render state
:
// 下面的外观可用于定义一个Viewer不可进入的不透明盒子
let appearance = new Cesium.PerInstanceColorAppearance({
translucent: false,
closed: true
})
// 下面的代码效果同上
let translucent = new Cesium.PerInstanceColorAppearance({
renderState: {
depthTest: {
enabled: true
},
cull: {
enabled: true,
face: Cesium.CullFace.BACK
}
}
})
一旦外观被创建,其render state
就不可再变,但是其材质是可以替换的。另外Primitive
的外观也是不可修改的。大部分外观具有flat
、faceForward
属性,可以间接的控制 GLSL 着色器:
flat
:扁平化着色,不考虑光线的作用;faceForward
:布尔值,控制光照效果。
Geometry 与 Appearance 的兼容性
需要注意,不是所有外观和所有几何图形可以搭配使用,例如EllipsoidSurfaceAppearance
与WallGeometry
就不能搭配,原因是后者是垂直于地表的。即使外观与几何图形兼容,它们还必须有匹配的顶点格式(vertex formats)—— 即几何图形必须具有外观可以作为输入的数据格式,在创建Geometry
时可以提供VertexFormat
。为了简便,可以让Geometry
计算所有顶点属性(vertex attributes),以使之适用于任何外观,但这样做效率较差:
let geometry = new Cesium.RectangleGeometry({
vertexFormat : Cesium.VertexFormat.ALL
})
而如果我们使用外观EllipsoidSurfaceAppearance
,其实只需要知道位置:
let geometry = new Ceisum.RectangleGeometry({
vertexFormat : Ceisum.VertexFormat.POSITION_ONLY
})
大部分外观具有vertexFormat
属性或者VERTEX_FORMAT
静态常量,创建形状时只需要使用这些顶点格式即可:
let geometry = new Ceisum.RectangleGeometry({
vertexFormat : Ceisum.EllipsoidSurfaceAppearance.VERTEX_FORMAT
})
let geometry2 = new Ceisum.RectangleGeometry({
vertexFormat : Ceisum.PerInstanceColorAppearance.VERTEX_FORMAT
})
let appearance = new Ceisum.MaterialAppearance()
let geometry3 = new Ceisum.RectangleGeometry({
vertexFormat : appearance.vertexFormat
})
此外,两个形状必须具有匹配的vertexFormat
,才能被合并到一个Primitive
中。
🎯 这篇文章是 Cesium 三维地球可视化从入门到进阶
专栏的第五篇文章,主要是对 Cesium
中创建图形常用的两种方法:Entity
、Primitive
做详解。
🎯 在后续的文章中, 将会分享更多
的实践案例
,如果你也对 三维可视化
比较感兴趣的话,欢迎关注我一起学习
🎯 Github 仓库地址:https://github.com/xushanpei/Cesium_Study_Cases
写在最后
公众号
:前端开发爱好者
专注分享web
前端相关技术文章
、视频教程
资源、热点资讯等,如果喜欢我的分享,给 🐟🐟 点一个赞
👍 或者 ➕关注
都是对我最大的支持。
大家好,我 xy,是一名前端 🤫 爱好:瞎折腾
如果你也是一名瞎折腾的前端欢迎加我微信交流哦...