【cesium知识梳理】5.Primitive绘制图形

4,464 阅读7分钟

之前学过、用过cesium,没有系统化的总结,现在整理一下,方便自己,也方便他人。

本文主要介绍Primitive绘制图形的用法,暂不涉及深层次的原理,等自己有时间了,可以尝试读一读源码,再写一些体会。

1.简单介绍

Primitive其实是 一种比较复杂的、面向图形开发人员的类。Primitive绘制图形的方式更接近渲染引擎底层。它绘制的图形有2部分组成:

1.Geometry,几何形状,定义Primitive图形的结构,如面、椭圆、线条等。

2.Appearance,外观,定义Primitive图形的渲染着色,通俗来讲,就是定义Primitive图形的外观材质。

在Primitive中,几何实例(Geometry)可以有一个或者多个,但是外观(Appearance)只能有一个,在使用的时候需要注意。

常用的几何图形如下所示:

几何形状用途描述
PolylineGeometry可以具有一定宽度的多段线
PolygonGeometry可以具有空洞或者拉伸高度的多边形
EllipseGeometry椭圆或者拉伸的椭圆
CircleGeometry圆或者拉伸的圆
CorridorGeometry走廊,沿地表且具有一定宽度
RectangleGeometry矩形或者拉伸的矩形
WallGeometry具有一定高度的墙
BoxGeometry以原点为中心点的立方体
EllipsoidGeometry以原点为中心点的椭球体
CylinderGeometry圆柱体或圆锥体
SphereGeometry以原点为中心点的球体

常用的几何外观如下所示

几何外观用途描述
MaterialAppearance任意几何外观,支持使用材质着色
EllipsoidSurfaceAppearance椭球体表面的几何外观,不支持几何
PerlnstanceColorAppearance让几何形状使用自定义颜色着色
PolylineMaterialAppearance多线段的几何外观,支持使用材质着
PolylineColorAppearance使用每个片段或顶点的颜色来着色多线段

创建primitive,先指定几何形状, 在指定外观,搭配起来,就完成了图形的绘制,但也不是任何一个形状和外观都可以搭配的,这个在实际使用中要注意。

2.绘制图形

(1)线 polyline

在定义几何形状时候,需要先new一个Cesium.GeometryInstance 几何实例,然后再去指定几何形状是什么。

//绘制线
//定义几何形状
var polyline = new Cesium.GeometryInstance({
    geometry: new Cesium.PolylineGeometry({
        positions: Cesium.Cartesian3.fromDegreesArray([
            108.0, 31.0, 100.0, 36.0, 105.0, 39.0,
        ]),
        width: 5.0,
    }),
});
//定义外观
var polylineAppearance = new Cesium.PolylineMaterialAppearance({
    material: Cesium.Material.fromType("Color", {
        color: new Cesium.Color(1.0, 1.0, 0.0, 1.0),
    }),
});
//创建Primitive
var addPolylineGeometry = new Cesium.Primitive({
    geometryInstances: polyline,
    appearance: polylineAppearance,
});

viewer.scene.primitives.add(addPolylineGeometry);

image.png

(2)多边形面 PolygonGeometry

//绘制面
//定义几何形状
var polygon = new Cesium.GeometryInstance({
        geometry: new Cesium.PolygonGeometry({
                polygonHierarchy: new Cesium.PolygonHierarchy(
                        Cesium.Cartesian3.fromDegreesArray([
                                108, 45, 109, 48, 104, 48, 103, 45,
                        ])
                ),
        }),
});
//定义外观
var polygonAppearance = new Cesium.MaterialAppearance({
        material: Cesium.Material.fromType("Dot"),
});
//创建Primitive
var addPolygonGeometry = new Cesium.Primitive({
        geometryInstances: polygon,
        appearance: polygonAppearance,
});

viewer.scene.primitives.add(addPolygonGeometry);

image.png

(3) 椭圆 EllipseGeometry

椭圆,用到了条纹材质

//绘制椭圆
//定义几何形状
var ellipse = new Cesium.GeometryInstance({
        geometry: new Cesium.EllipseGeometry({
                center: Cesium.Cartesian3.fromDegrees(105, 40.0),
                semiMajorAxis: 500000.0,
                semiMinorAxis: 300000.0,
                //rotation: Cesium.Math.toRadians(60.0)
        }),
});
//定义外观
var ellipseAppearance = new Cesium.EllipsoidSurfaceAppearance({
        material: Cesium.Material.fromType("Stripe"),//条纹
});
//创建Primitive
var addEllipseGeometry = new Cesium.Primitive({
        geometryInstances: ellipse,
        appearance: ellipseAppearance,
});

viewer.scene.primitives.add(addEllipseGeometry);

image.png

(4)圆 CircleGeometry

圆形,用上了网格材质

//绘制圆
//定义几何形状
var circle = new Cesium.GeometryInstance({
    geometry: new Cesium.CircleGeometry({
        center: Cesium.Cartesian3.fromDegrees(100, 45.0),
        radius: 300000.0,
    }),
});

//定义外观
var circleAppearance = new Cesium.EllipsoidSurfaceAppearance({
    material: Cesium.Material.fromType("Grid"),//网格
});

//创建Primitive
var addCircleGeometry = new Cesium.Primitive({
    geometryInstances: circle,
    appearance: circleAppearance,
});
viewer.scene.primitives.add(addCircleGeometry);

image.png

(5)走廊 CorridorGeometry

var corridor = new Cesium.GeometryInstance({
        geometry: new Cesium.CorridorGeometry({
                positions: Cesium.Cartesian3.fromDegreesArray([
                        100.0, 40.0, 105.0, 35.0, 102.0, 33.0,
                ]),
                width: 100000,
        }),
        attributes: {
                //颜色,红绿蓝,透明度
                color: new Cesium.ColorGeometryInstanceAttribute(
                        0.8,
                        0.5,
                        0.8,
                        0.7
                ),
        },
});
//定义外观
var corridorAppearance = new Cesium.PerInstanceColorAppearance({
        flat: true, //外观是平的
        translucent: true,  //半透明的
});
//创建Primitive
var addCorridorGeometry = new Cesium.Primitive({
        geometryInstances: corridor,
        appearance: corridorAppearance,
});

viewer.scene.primitives.add(addCorridorGeometry);

image.png

(6)矩形 RectangleGeometry

//绘制矩形
//定义几何形状
var rectangle = new Cesium.GeometryInstance({
        geometry: new Cesium.RectangleGeometry({
                rectangle: Cesium.Rectangle.fromDegrees(
                        95.0,
                        39.0,
                        100.0,
                        42.0
                ),
                // height: 10000.0,
        }),
});
//定义外观
var rectangleAppearance = new Cesium.EllipsoidSurfaceAppearance({
        material: Cesium.Material.fromType("Water"),
});
//创建Primitive
var addRectangleGeometry = new Cesium.Primitive({
        geometryInstances: rectangle,
        appearance: rectangleAppearance,
});
viewer.scene.primitives.add(addRectangleGeometry);

image.png

(7)墙 WallGeometry

//绘制墙
//定义几何形状
var wall = new Cesium.GeometryInstance({
        geometry: new Cesium.WallGeometry({
                positions: Cesium.Cartesian3.fromDegreesArrayHeights([
                        107.0, 43.0, 100000.0, 97.0, 43.0, 100000.0, 97.0, 40.0,
                        100000.0, 107.0, 40.0, 100000.0, 107.0, 43.0, 100000.0,
                ]),
        }),
});
//定义外观
var wallAppearance = new Cesium.MaterialAppearance({
        material: Cesium.Material.fromType("Color"),
});
//创建Primitive
var addWallGeometry = new Cesium.Primitive({
        geometryInstances: wall,
        appearance: wallAppearance,
});
viewer.scene.primitives.add(addWallGeometry);

image.png

除了绘制这些基本的图形外,primitive还可以绘制 立方体,圆柱体,椭球体等,其中会涉及到矩阵变换四元数等概念,这些概念比较抽象,这里先留个坑,后续等我有了更深刻的理解,再专门写文章聊一聊。

3.primitive 绘制贴地

entity可以贴地绘制图形,primitive也是可以的,答案就是GroundPrimitive

对于CircleGeometry(圆)、CorridorGeometry(走廊)、EllipseGeometry(椭圆)、PolygonGeometry(多边形)和RectangleGeometry(矩形)等几何图形,Cesium提供了GroundPrimitive类,可以实现贴地。

对于线,

Cesium提供了专门的贴地线GroundPolylinePrimitive和相对应的贴地线几何图形 GroundPolylineGeometry。

其他方面和primitive绘制图形一样,没有什么变化。


primitive绘制贴地图形还有2个要求:

1.GroundPrimitive中的几何图形必须来自单个几何图形,目前还不支持对多个几何图形进行批处理。

2.不支持对立体几何图形的贴地。


(1)线 贴地

重点是,

geometry: new Cesium.GroundPolylineGeometry

new Cesium.GroundPolylinePrimitive

//绘制线
//定义几何形状
var polyline = new Cesium.GeometryInstance({
    geometry: new Cesium.GroundPolylineGeometry({
        //贴地线
        positions: Cesium.Cartesian3.fromDegreesArray([
            108.0, 31.0, 100.0, 36.0, 105.0, 39.0,
        ]),
        width: 5.0,
    }),
});
//定义外观
var polylineAppearance = new Cesium.PolylineMaterialAppearance({
    material: Cesium.Material.fromType("Color"),
});
//创建GroundPrimitive
var addGroundPolylinePrimitive = new Cesium.GroundPolylinePrimitive(
    {
        //仅支持GroundPolylineGeometry
        geometryInstances: polyline,
        appearance: polylineAppearance,
    }
);

viewer.scene.primitives.add(addGroundPolylinePrimitive);

(2)面 贴地

new Cesium.GroundPrimitive

//绘制面
//定义几何形状
var polygon = new Cesium.GeometryInstance({
    geometry: new Cesium.PolygonGeometry({
        polygonHierarchy: new Cesium.PolygonHierarchy(
            Cesium.Cartesian3.fromDegreesArray([
                110.0, 30.0, 114.0, 38.0, 106.0, 35.0, 108.0, 30.0,
            ])
        ),
    }),
});
//定义外观
var polygonAppearance = new Cesium.MaterialAppearance({
    material: Cesium.Material.fromType("Color", {
        color: new Cesium.Color(0.5, 0.8, 0.0, 0.6),
    }),
    faceForward: true,
});
//创建GroundPrimitive
var addPolygonGroundPrimitive = new Cesium.GroundPrimitive({
    //贴地面
    geometryInstances: polygon,
    appearance: polygonAppearance,
});


viewer.scene.primitives.add(addPolygonGroundPrimitive);

(3)其他贴地

对于CircleGeometry(圆)、CorridorGeometry(走廊)、EllipseGeometry(椭圆)、PolygonGeometry(多边形)和RectangleGeometry(矩形),他们的贴地方式都是一样的,需要在实例化primitive 的时候,使用GroundPrimitive,这里就不再赘述。

4.primitive 管理

(1)合并绘制

一个primitive,由2部分组成,

1.几何实例

2.外观

几何实例可以有一个或多个,外观只有一个,当有多个几何实例的时候,就可以称之为合并绘制

这里我们绘制2个矩形实例,共用一个外观,绘制一下:

// 创建几何体实例1
let instance1 = new Cesium.GeometryInstance({
    geometry: new Cesium.RectangleGeometry({
        rectangle: Cesium.Rectangle.fromDegrees(115, 20, 135, 30),
        // 距离表面高度
        height: 0,
        vertexFormat:
            Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
    }),

    attributes: {
        color: Cesium.ColorGeometryInstanceAttribute.fromColor(
            Cesium.Color.RED.withAlpha(0.5)
        ),
    },
});

// 创建几何体实例2
let instance2 = new Cesium.GeometryInstance({
    geometry: new Cesium.RectangleGeometry({
        rectangle: Cesium.Rectangle.fromDegrees(140, 20, 160, 30),
        height: 0,
        vertexFormat:
            Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
    }),

    attributes: {
        color: Cesium.ColorGeometryInstanceAttribute.fromColor(
            Cesium.Color.BLUE.withAlpha(0.5)
        ),
    },
});

// 03-设置外观
let appearance = new Cesium.PerInstanceColorAppearance({
    flat: true,
});
// 04-实例化primitive,两个实例,1个外观
let primitive = new Cesium.Primitive({
    geometryInstances: [instance1, instance2],
    appearance: appearance,
});
// 05-添加到viewer
viewer.scene.primitives.add(primitive);

image.png

我们还可以玩一个更厉害的,直接绘制绘制2592个铺满整个地球的半透明、颜色随机的矩形。代码如下:

//合并多个矩形
var instances = []; //存储几何实例
for (var lon = -180.0; lon < 180.0; lon += 5.0) {
    for (var 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,
                }),
                id: lon + "-" + lat,
                attributes: {
                    color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                        Cesium.Color.fromRandom({
                            alpha: 0.6,
                        })
                    ),
                },
            })
        );
    }
}

//创建Primitive
var mergeInstances = new Cesium.Primitive({
    geometryInstances: instances,
    appearance: new Cesium.PerInstanceColorAppearance(),
});

viewer.scene.primitives.add(mergeInstances);

image.png

通过循环嵌套,以经纬度5度为增量,绘制出两千多个小矩形,他们都是几何实例,存储在数组中,最终通过primitive绘制出来。

在cesium中,绘制大量图形时,用primitive是很有优势的(以下4句话来自chatGPT):

  1. 性能优势Primitive是Cesium中的一种渲染机制,专门用于绘制大量的几何图形。它在渲染大量图形时比较高效,能够更好地处理复杂的场景,而不会影响帧率。
  2. 批量处理Primitive允许您将多个几何实例组合成一个渲染批次,从而减少了渲染调用的次数。这有助于减少CPU和GPU之间的数据传输,提高了渲染效率。
  3. 自动优化:Cesium的Primitive会自动根据视野和距离进行优化,只渲染那些在视野范围内的图形,从而减少不必要的渲染负担。
  4. 内存优化:由于Primitive是针对大量图形设计的,它能够更有效地管理内存,减少不必要的资源占用。

(2)拾取

Cesium中有多种拾取方法,其中,Scene类中的Pick方法用于拾取指定位置顶端的一个Primitive属性对象。我们可以通过该方法来获取几何图形,并对其进行某些操作。

现在我们就实现一个功能,当我们点击某个几何图形的时候,修改它的颜色为黄色。

下面这段代码要在上面代码的基础上使用,假定你已经使用了上面的代码,生成了2千多个矩形。

var handler = new Cesium.ScreenSpaceEventHandler(
	viewer.scene.canvas
);
//监听鼠标左键点击事件
handler.setInputAction(function (movement) {
	//获取鼠标点击位置的实例
	var pick = viewer.scene.pick(movement.position);

	//被点击的实例变为黄色
	if (Cesium.defined(pick) && typeof pick.id == "string") {
		let attributes =
			mergeInstances.getGeometryInstanceAttributes(pick.id);
		attributes.color =
			Cesium.ColorGeometryInstanceAttribute.toValue(
				Cesium.Color.YELLOW.withAlpha(0.9)
			);
	}
	//弹窗展示被点击的实例ID
	alert("点击实例id为:" + pick.id);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

image.png

当我们点击某个矩形,弹窗+变色

image.png

多点击几个位置

image.png

(3)修改属性

实际上,修改属性已经在上面的例子中用过了,先通过id获取实例属性,再修改

let attributes =
	mergeInstances.getGeometryInstanceAttributes(pick.id);
attributes.color =
	Cesium.ColorGeometryInstanceAttribute.toValue(
		Cesium.Color.YELLOW.withAlpha(0.9)
	);

(4)移除

移除指定的peimitive对象

viewer.scene.primitives.remove(addCylinderGeometry)

移除全部的primitive对象

viewer.scene.primitives.removeAll()