【Cesium 入门】自定义几何形状和外观

1,044 阅读9分钟

【Cesium 入门】系列是将CesiumJS官网教程文章翻译为中文,同时对文章进行了一定的补充,并且提供Vue3版本的代码GitHub - cesiumjs教程的vue3版本  。希望这个系列可以帮助你更好的学习cesium。

本教程将向您介绍 Primitive API 提供的几何和外观系统。这是一个使用自定义网格、形状、体积和外观扩展 CesiumJS的高级主题。 如果你只是要在Cesium中绘制简单的立体形状,可以直接使用Entity实体类(Entity,下文中实体都是),可参考实体教程,Primitive在下文中称为基元。

下文中特殊名称说明:

  1. 基元: Primitive, Cesium中图形的低级原始API
  2. 实体:Entity , 基于Primitive API封装的高级类Entity
  3. 几何实例: Primitive中的geometryInstances属性
  4. 外观:图形的外观,也是Primitive中的appearence属性

本教程将讲述以下几点:

  • 几何概述
  • 几何类型
  • 组合几何形状
  • 拾取
  • 几何实例
  • 更新每个实例的属性
  • 外观
  • 几何和外观的兼容性

几何概述

CesiumJS 可以使用实体创建不同的几何类型,例如多边形和椭球体。例如,将以下内容复制并粘贴到 Hello World Sandcastle 示例中,以在地球上创建一个带有点图案的矩形:

const viewer = new Cesium.Viewer("cesiumContainer"); 
viewer.entities.add({ 
    rectangle: { 
        coordinates: Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0), 
        material: new Cesium.StripeMaterialProperty({ 
            evenColor: Cesium.Color.WHITE, 
            oddColor: Cesium.Color.BLUE, 
            repeat: 5, 
        }), 
    }, 
 });

image.png

在本教程中,我们将深入了解构成它们的几何和外观类型。几何定义了图元的结构,即组成图元的三角形、线或点。外观定义图元的着色,包括其完整的 GLSL 顶点和片段着色器以及渲染状态

使用几何形状和外观的好处是:

  • 性能 - 当绘制大量静态图元(例如美国每个邮政编码的多边形)时,直接使用几何图形可以让我们将它们组合成单个几何图形,以减少 CPU 开销并更好地利用 GPU。组合基元 (Combining primitives) 是在 Web Worker 上完成的,以保持 UI 的响应能力。
  • 灵活性 - 基元结合了几何形状和外观。通过将它们解耦,我们可以独立地修改它们。我们可以添加与许多不同外观兼容的新几何形状,反之亦然。
  • 低级访问 - 外观提供了近乎真实的渲染访问,而无需担心直接使用渲染器的所有细节.
    外观使您可以轻松地:
    • 编写完整的 GLSL 顶点和片段着色器。
    • 使用自定义渲染状态。

也有一些缺点:

  • 直接使用几何和外观需要更多的代码和对图形更深入的理解。实体处于适合地图应用程序的抽象级别;几何形状和外观具有更接近传统 3D 引擎的抽象级别。
  • 组合几何对于静态数据有效,但对于动态数据不一定有效。

让我们使用几何和外观重写最初的代码示例:

const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

// original code
//viewer.entities.add({
//    rectangle : {
//        coordinates: Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0),
//        material: new Cesium.StripeMaterialProperty({
//            evenColor: Cesium.Color.WHITE,
//            oddColor: Cesium.Color.BLUE,
//            repeat: 5
//        })
//    }
//});

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,
  }),
});

scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: instance,
    appearance: new Cesium.EllipsoidSurfaceAppearance({
      material: Cesium.Material.fromType("Stripe"),
    }),
  })
);

我们没有使用矩形实体,而是使用了通用Primitive,它结合了几何实例(geometryInstances)和外观(appearance)。目前,我们不会区分 Geometry 和 GeometryInstance,除非实例是几何图形的容器。
为了创建矩形的几何形状,即覆盖矩形区域并适合地球曲率的三角形,我们创建一个 RectangleGeometry。

image.png

由于它在地球表面上,我们可以使用 EllipsoidSurfaceAppearance。这通过基于几何体位于地球表面表面上或椭球体上方恒定高度的事实进行假设来节省内存。

几何类型

上面代码使用了new Cesium.RectangleGeometry创建了满足地球曲率的矩形,对于几何实例geometryInstances还有其他设定好的几何类,如下表所示:

GeometryOutlineDescription
BoxGeometryBoxOutlineGeometry盒子
CircleGeometryCircleOutlineGeometry圆或挤压圆
CorridorGeometryCorridorOutlineGeometry垂直于表面的多段线,宽度以米为单位,可选的拉伸高度
CylinderGeometryCylinderOutlineGeometry圆柱体、圆锥体或截锥体
EllipseGeometryEllipseOutlineGeometry椭圆或挤压椭圆
EllipsoidGeometryEllipsoidOutlineGeometry椭球体
RectangleGeometryRectangleOutlineGeometry矩形或拉伸矩形
PolygonGeometryPolygonOutlineGeometry具有可选孔或拉伸多边形的多边形
PolylineGeometrySimplePolylineGeometry宽度以像素为单位的线段集合
PolylineVolumeGeometryPolylineVolumeOutlineGeometry沿多段线挤压的 2D 形状
SphereGeometrySphereOutlineGeometry球体
WallGeometryWallOutlineGeometry垂直于地球的墙

image.png

组合几何形状

当我们使用一个基元绘制多个静态几何图形时,我们会看到性能优势。例如,在一个图元中绘制两个矩形。

const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

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,
  }),
});

const anotherInstance = new Cesium.GeometryInstance({
  geometry: new Cesium.RectangleGeometry({
    rectangle: Cesium.Rectangle.fromDegrees(-85.0, 20.0, -75.0, 30.0),
    vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
  }),
});

scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: [instance, anotherInstance],
    appearance: new Cesium.EllipsoidSurfaceAppearance({
      material: Cesium.Material.fromType("Stripe"),
    }),
  })
);

image.png

我们创建了两个不同位置的矩形实例,然后将这两个实例提供给基元。这会以相同的外观绘制两个实例。
某些外观允许每个实例提供独特的属性。例如,我们可以使用 PerInstanceColorAppearance 用实例自定义颜色为每个实例着色。

const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

const instance = new Cesium.GeometryInstance({
  geometry: new Cesium.RectangleGeometry({
    rectangle: Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0),
    vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
  }),
  attributes: {
    color: new Cesium.ColorGeometryInstanceAttribute(0.0, 0.0, 1.0, 0.8),
  },
});

const anotherInstance = new Cesium.GeometryInstance({
  geometry: new Cesium.RectangleGeometry({
    rectangle: Cesium.Rectangle.fromDegrees(-85.0, 20.0, -75.0, 30.0),
    vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
  }),
  attributes: {
    color: new Cesium.ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 0.8),
  },
});

scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: [instance, anotherInstance],
    appearance: new Cesium.PerInstanceColorAppearance(),
  })
);

image.png

每个实例都有一个颜色属性。该基元是使用 PerInstanceColorAppearance 构建的,它使用每个实例的颜色属性来确定外观。
组合几何图形允许 CesiumJS 有效地绘制大量几何图形。以下示例绘制 2,592 个独特颜色的矩形。

const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

const instances = [];

for (let lon = -180.0; lon < 180.0; lon += 5.0) {
  for (let lat = -85.0; lat < 85.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
          ),
          vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
        }),
        attributes: {
          color: Cesium.ColorGeometryInstanceAttribute.fromColor(
            Cesium.Color.fromRandom({ alpha: 0.5 })
          ),
        },
      })
    );
  }
}

scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: instances,
    appearance: new Cesium.PerInstanceColorAppearance(),
  })
);

拾取

几何实例合并后可以独立访问。为几何实例分配一个ID,并使用它来确定是否使用 Scene.pick 选取了该实例。 以下示例创建一个具有 ID 的实例,并在单击该实例时向控制台写入一条消息。

const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

const instance = new Cesium.GeometryInstance({
  geometry: new Cesium.RectangleGeometry({
    rectangle: Cesium.Rectangle.fromDegrees(-100.0, 30.0, -90.0, 40.0),
    vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
  }),
  id: "my rectangle",
  attributes: {
    color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED),
  },
});

scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: instance,
    appearance: new Cesium.PerInstanceColorAppearance(),
  })
);

const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction((movement) => {
  const pick = scene.pick(movement.position);
  if (Cesium.defined(pick) && pick.id === "my rectangle") {
    console.log("Mouse clicked rectangle.");
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

使用 id 可以避免在构造基元后在内存中保留对完整实例(包括几何图形)的引用。

几何实例

实例可用于在场景的不同部分中定位、缩放和旋转相同的几何体。因为多个实例可以引用相同的几何图形,并且每个实例可以有不同的 modelMatrix。这使得我们只需计算一次几何图形即可多次重复使用。

image.png 以下示例创建一个 EllipsoidGeometry 和两个实例。每个实例都引用相同的椭球体几何形状,但使用不同的 modelMatrix 放置它,从而导致一个椭球体位于另一个椭球体之上。

const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

const ellipsoidGeometry = new Cesium.EllipsoidGeometry({
  vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
  radii: new Cesium.Cartesian3(300000.0, 200000.0, 150000.0),
});

const cyanEllipsoidInstance = new Cesium.GeometryInstance({
  geometry: ellipsoidGeometry,
  modelMatrix: Cesium.Matrix4.multiplyByTranslation(
    Cesium.Transforms.eastNorthUpToFixedFrame(
      Cesium.Cartesian3.fromDegrees(-100.0, 40.0)
    ),
    new Cesium.Cartesian3(0.0, 0.0, 150000.0),
    new Cesium.Matrix4()
  ),
  attributes: {
    color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.CYAN),
  },
});

const orangeEllipsoidInstance = new Cesium.GeometryInstance({
  geometry: ellipsoidGeometry,
  modelMatrix: Cesium.Matrix4.multiplyByTranslation(
    Cesium.Transforms.eastNorthUpToFixedFrame(
      Cesium.Cartesian3.fromDegrees(-100.0, 40.0)
    ),
    new Cesium.Cartesian3(0.0, 0.0, 450000.0),
    new Cesium.Matrix4()
  ),
  attributes: {
    color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.ORANGE),
  },
});

scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: [cyanEllipsoidInstance, orangeEllipsoidInstance],
    appearance: new Cesium.PerInstanceColorAppearance({
      translucent: false,
      closed: true,
    }),
  })
);

image.png

更新每个实例的属性

将几何图形添加到图元后更新其每个实例属性以更改可视化效果。每个实例的属性包括:

  • Color :确定实例颜色的 ColorGeometryInstanceAttribute。该图元必须具有 PerInstanceColorAppearance。
  • Show :确定实例可见性的布尔值。适用于任何实例。
    此示例显示如何更改几何实例的颜色:
const viewer = new Cesium.Viewer("cesiumContainer");
const scene = viewer.scene;

const 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)
    ),
  },
  id: "circle",
});

const primitive = new Cesium.Primitive({
  geometryInstances: circleInstance,
  appearance: new Cesium.PerInstanceColorAppearance({
    translucent: false,
    closed: true,
  }),
});
scene.primitives.add(primitive);

setInterval(() => {
  const attributes = primitive.getGeometryInstanceAttributes("circle");
  attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(
    Cesium.Color.fromRandom({ alpha: 1.0 })
  );
}, 2000);

可以使用primitive.getGeometryInstanceAttributes 从图元中检索几何实例的属性。可以直接更改属性的属性。

外观

几何定义结构。图元的另一个关键属性外观定义了图元的阴影,即各个像素的着色方式。一个图元可以有许多几何实例,但它只能有一种外观。根据外观的类型,外观将具有定义大部分阴影的材质(bulk of the shading)。

image.png

CesiumJS有以下几种类型的外观:
AppearanceDescription
MaterialAppearance适用于所有几何类型并支持描述阴影的材质的外观。
EllipsoidSurfaceAppearanceMaterialAppearance 的一个版本,假设几何体与地球表面平行,如多边形,并使用此假设通过程序计算许多顶点属性来节省内存。
PerInstanceColorAppearance使用每个实例的颜色来为每个实例着色。
PolylineMaterialAppearance支持对折线进行着色的材质。
PolylineColorAppearance使用每顶点或每段着色来为折线着色。

外观定义了绘制图元时在 GPU 上执行的完整 GLSL 顶点和片段着色器。外观还定义了完整的渲染状态,它控制绘制图元时 GPU 的状态。我们可以直接定义渲染状态或使用更高级别的属性,例如关闭和半透明,外观将转换为渲染状态。例如:

// Perhaps for an opaque box that the viewer will not enter.
//  - Backface culled and depth tested.  No blending.

const appearance = new Cesium.PerInstanceColorAppearance({
  translucent: false,
  closed: true,
});

// This appearance is the same as above
const anotherAppearance = new Cesium.PerInstanceColorAppearance({
  renderState: {
    depthTest: {
      enabled: true,
    },
    cull: {
      enabled: true,
      face: Cesium.CullFace.BACK,
    },
  },
});

创建外观后,我们无法更改其 renderState 属性,但我们可以更改其材质。我们还可以更改图元的外观属性。 大多数外观还具有 flat 和faceForward 属性,它们间接控制 GLSL 着色器。

  • flat - 平面阴影。不考虑照明。
  • faceForward - 照明时,翻转法线,使其始终面向观察者。避免背面出现黑色区域​​,例如墙的内部。

image.png

几何和外观兼容性

并非所有外观都适用于所有几何形状。例如,EllipsoidSurfaceAppearance 不适用于 WallGeometry,因为墙壁不在地球表面。
为了使外观与几何体兼容,它们必须具有匹配的顶点格式,这意味着几何体必须具有外观期望作为输入的数据。创建几何图形时可以提供 VertexFormat。

image.png

image.png

几何图形的 vertexFormat 决定它是否可以与另一个几何图形组合。两个几何图形不必是相同的类型,但它们需要匹配的顶点格式。
为了方便起见,外观具有 vertexFormat 属性或 VERTEX_FORMAT 静态常量,可以作为几何体的选项传递。

const geometry = new Cesium.RectangleGeometry({
  vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
  // ...
});

const geometry2 = new Cesium.RectangleGeometry({
  vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
  // ...
});

const appearance = new Cesium.MaterialAppearance(/* ... */);
const geometry3 = new Cesium.RectangleGeometry({
  vertexFormat: appearance.vertexFormat,
  // ...
});

到这里本教程就结束啦

源码可从_GitHub - cesiumjs教程的vue3版本_下载

如果文章对你有帮助,希望可以收获你的点赞收藏加关注,我会持续带来更多有价值的内容!