三维地球可视化从入门到进阶 - 实体 Entity、图形 Primitive 详解

2,148 阅读11分钟

本文为稀土掘金技术社区首发签约文章,14 天内禁止转载,14 天后未获授权禁止转载,侵权必究!

哈喽,大家好 我是 xy👨🏻‍💻。这篇文章是 Cesium 三维地球可视化从入门到进阶专栏的第五篇。主要是对 Cesium 中创建图形常用的两种方法:EntityPrimitive 做详解。

截至目前,Cesium 基础知识基本上都讲解的差不多了,如果你对基础的一些使用方式还是不了解的话,可以参考前面几节内容。

后续的文章将会以分享案例的形式分享,如果你也喜欢 三维地球可视化 , 记得一定要关注我。

Entity 实体简介

实体(entity)是 Cesium 中自带的创建图形的方法,通过该方法可以在场景中创建线多边形立方体圆等基本图形。 截止目前为止,Cesium 中支持 17 种实体:

实体名称类型描述图例
billboardBillboardGraphics广告牌,可以自定义为图片或文字
boxBoxGraphics盒子
corridorCorridorGraphicscorridor直译为走廊,实际上是将多个点相连形成的一种实体。
cylinderCylinderGraphics圆柱体,可以通过设置上下半径生成锥体
ellipseEllipseGraphics椭圆
ellipsoidEllipsoidGraphics椭球体
labelLabelGraphics标签
modelModelGraphics模型,主要用于加载 glTF 模型
tilesetCesium3DTilesetGraphics瓦片集合,主要用于加载 3D tiles 模型
pathPathGraphics路径,实际上是将大量的点连起来绘制的一种线条实体
planePlaneGraphics平面
pointPointGraphics
polygonPolygonGraphics多边形
polylinePolylineGraphics折线
polylineVolumePolylineVolumeGraphics折线体
rectangleRectangleGraphics矩形
wallWallGraphics

简单示例

下面以创建一个盒子为例,介绍创建一个实体的基本方法:

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,不同的实体表达的图形不一样,其所需属性也不同,下面会依次介绍不同实体的创建方法。

其中idname属性即使不指定,也会成功创建实体并且不会报错,但是若缺少位置信息,程序虽不会报错,但是无法成功创建该实体。

盒子 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)其中的三个参数分别代表笛卡尔空间直角坐标系的的xyz,可以简单理解为设置盒子的长宽高,如下图:

可视距离: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.NONENumber绝对高度,即在position中设置的高度就是最后的显示高度
Cesium.HeightReference.CLAMP_TO_GROUNDNumber浮于地表,无论在position中设置怎么样的高度值,点的位置始终浮于地表,即固定在地形上
Cesium.HeightReference.RELATIVE_TO_GROUNDNumber相对于地形的高度,即在position中设置的高度值是在地形高度的基础上进行计算的

距离缩放:scaleByDistance

scaleByDistance属性用于指定点在不同的可视距离下放大或缩小的倍数,如下面代码:

point: {
  scaleByDistance: new Cesium.NearFarScalar(100, 2, 10000, 15), // 100米以下放大2倍,10000米以上放大15倍
}

NearFarScalar(near, nearValue, far, farValue)对象接收四个参数:

参数值值的类型默认值描述
nearNumber0.0最近距离
nearValueNumber0.0小于最近距离时,放大或缩小的倍数
farNumber1.0最远距离
farValueNumber0.0大于最远距离时,放大或缩小的倍数

其效果图如下,随着距离的变化,到临界值时其点的大小也发生改变:

距离透明:translucencyByDistance

scaleByDistance属性用于指定点在不同的可视距离下的透明度,如下面代码:

point: {
  translucencyByDistance: new Cesium.NearFarScalar(100, 0.2, 10000, 0.8), // 100米以下透明度为0.2,10000米以上透明度为0.8
}

参数依旧是NearFarScalar不过参数的意义稍有区别:

参数值值的类型默认值描述
nearNumber0.0最近距离
nearValueNumber0.0小于最近距离时的透明度
farNumber1.0最远距离
farValueNumber0.0大于最远距离时的透明度

其效果图如下,随着距离的变化,到临界值时其点的透明度也发生变化:

Primitive 图形简介

图形(Primitive)是 Cesium 中更加高阶的创建图形的方法,那么相对低阶的方法就是使用实体(Entity)定义一个图形。当创建一个图形时,两者的流程都是定义实体的尺寸大小和定义实体的材质外观。图形(Primitive)由两部分组成:

  1. 几何形状(Geometry):定义了 Primitive 的结构,例如三角形、线条、点等;
  2. 外观(Appearance ):定义 Primitive 的着色(Sharding),包括 GLSL(OpenGL 着色语言,OpenGL Shading Language)顶点着色器和片段着色器( vertex and fragment shaders),以及渲染状态(render state)。

图形(Primitive)的优劣

但图形(Primitive)相较于实体(Entity)有如下优势:

  1. 性能:绘制大量 Primitive 时,可以将其合并为单个 Geometry 以减轻 CPU 负担、更好的使用 GPU。合并 Primitive 由 web worker 线程执行,UI 保持响应性;
  2. 灵活性:Geometry 与 Appearance 解耦,两者可以分别进行修改;
  3. 低级别访问:易于编写 GLSL 顶点、片段着色器、使用自定义的渲染状态 。

同时,图形(Primitive)有如下劣势:

  1. 需要编写更多地代码;
  2. 需要对图形编程有更多的理解,特别是 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
截锥体

仅有轮廓线的截锥体
GroundPolylineGeometryGroundPolylineGeometry地面多段线
PlaneGeometry

PlaneOutlineGeometry
PlaneGeometry

PlaneOutlineGeometry
平面

仅有轮廓线的平面
PolygonGeometry

PolygonOutlineGeometry
PolygonGeometry

PolygonOutlineGeometry
折线

仅有轮廓线的折线
PolylineVolumeGeometry

PolylineVolumeOutlineGeometry
PolylineVolumeGeometry

PolylineVolumeOutlineGeometry
折线体

仅有轮廓线的折线体
RectangleGeometry

RectangleOutlineGeometry
RectangleGeometry

RectangleOutlineGeometry
矩形

仅有轮廓线的矩形
SimplePolylineGeometrySimplePolylineGeometry简单折线
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中以后,让然可以修改几何图形的某些属性:

  1. 颜色:如果Primitive设置了PerInstanceColorAppearance外观,则可以修改ColorGeometryInstanceAttribute类型的颜色;
  2. 可见性:任何实例可以修改可见性。

代码如下:

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 支持下表列出的外观:

外观名称类型描述
MaterialAppearanceMaterialAppearance支持各种 Geometry 类型的外观,支持使用材质来定义着色
EllipsoidSurfaceAppearanceEllipsoidSurfaceAppearanceMaterialAppearance 的一个版本。假设几何图形与地表是平行的,并且依此来进行顶点属性(vertex attributes)的计算
PerInstanceColorAppearancePerInstanceColorAppearance让每个实例使用自定义的颜色来着色
PolylineMaterialAppearancePolylineMaterialAppearance支持使用材质来着色多段线
PolylineColorAppearancePolylineColorAppearance使用每顶点或者每片段(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的外观也是不可修改的。大部分外观具有flatfaceForward属性,可以间接的控制 GLSL 着色器:

  1. flat:扁平化着色,不考虑光线的作用;
  2. faceForward:布尔值,控制光照效果。

Geometry 与 Appearance 的兼容性

需要注意,不是所有外观和所有几何图形可以搭配使用,例如EllipsoidSurfaceAppearanceWallGeometry就不能搭配,原因是后者是垂直于地表的。即使外观与几何图形兼容,它们还必须有匹配的顶点格式(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 中创建图形常用的两种方法:EntityPrimitive 做详解。

🎯 在后续的文章中, 将会分享更多实践案例,如果你也对 三维可视化比较感兴趣的话,欢迎关注我一起学习

🎯 Github 仓库地址:https://github.com/xushanpei/Cesium_Study_Cases

写在最后

公众号前端开发爱好者 专注分享 web 前端相关技术文章视频教程资源、热点资讯等,如果喜欢我的分享,给 🐟🐟 点一个 👍 或者 ➕关注 都是对我最大的支持。

大家好,我 xy,是一名前端 🤫 爱好:瞎折腾

如果你也是一名瞎折腾的前端欢迎加我微信交流哦...

🤫 一定要点我