自定义几何形状和外观
有关Primitive API可用的Geometry & Appearance系统的信息,这是使用自定义网格、形状、体积和外观扩展CesiumJS的高级主题,并不适合典型的Cesium用户。
几何概述
Geometry定义了图元的结构,即构成图元的三角形、直线或点。
Appearance定义了图元的着色,包括其完整的GLSL顶点和片段着色器以及渲染状态。
使用Geometry和Appearance的好处是:
- 性能:当绘制大量静态图元时,直接使用Geometry可以使我们将它们组合成一个几何图形,减少CPU开销并更好地利用GPU。组合原语是在web worker上完成的,以保持UI的响应速度。
- 灵活性:primitives结合了Geometry和Appearance。通过解耦它们,我们可以添加与许多不同外观兼容的新几何形状,反之亦然。
- 低级访问:外观可提供接近金属的渲染访问权限,而不必担心Renderer直接使用Direct的所有细节。Appearance使我们可以轻松地:
- 编写完整的GLSL顶点和片段着色器
- 使用自定义渲染状态
还有一些缺点:
- 直接使用几何形状和外观需要更多的代码和对图形的更深刻理解。实体处于适用于映射应用程序的抽象级别;几何形状和外观具有接近传统3D引擎的抽象水平。
- 组合几何形状对于静态数据有效,而对于动态数据则不一定有效。
var viewer = new Cesium.Viewer('cesiumContainer');
var 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
// })
// }
//});
var 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')
})
}));
在上面的代码中没有使用entities,而是使用Primitive
来代替,它结合了GeometryInstance和Appearance。
创建矩形的几何形状,即覆盖矩形区域并适合地球曲率的三角形,我们创建一个RectangleGeometry。
图形组合
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
//第一个矩形实例
var 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
})
});
//第二个矩形实例
var 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')
})
}));
我们用另一个矩形创建了另一个实例,然后将两个实例都提供给图元(Primitive)。这将以相同的外观绘制两个实例。
一些外观允许每个实例提供唯一的属性。例如,我们可以使用PerInstanceColorAppearance不同的颜色为每个实例着色。
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var 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)
}
});
var 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()
}));
上面的每个实例都有一个color
属性,确定图形的颜色。
组合几何图形可以使CesiumJS有效地绘制大量几何图形。下面的例子绘制了2592个唯一着色的矩形。
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
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
}),
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的实例,当它被选中的时候会发送一条消息到控制台。
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var 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()
}));
var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(function (movement) {
var pick = scene.pick(movement.position);
if (Cesium.defined(pick) && (pick.id === 'my rectangle')) {
console.log('Mouse clicked rectangle.');
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
使用id可以避免在构造原语之后在内存中保留对整个实例(包括几何图形)的引用。
图形实例
实例可以用来在场景的不同部分定位、缩放和旋转相同的几何体。因为多个实例可以引用相同的Geometry,每个实例可以有一个不同的模型矩阵。这允许我们只计算几何一次,并重用它多次。
下面的例子创建了一个EllipsoidGeometry
和两个实例。每个实例引用相同的椭圆形状,但通过使用不同的modelMatrix
将一个椭圆形放在另一个的上面。
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var ellipsoidGeometry = new Cesium.EllipsoidGeometry({
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
radii : new Cesium.Cartesian3(300000.0, 200000.0, 150000.0)
});
var 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)
}
});
var 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
})
}));
更新每一个实例的属性
当几何实例被添加到原语中后,更新每个实例的属性来改变外观。这些属性包括:
- Color:
ColorGeometryInstanceAttribute
决定了实例的颜色。原语必须有PerInstanceColorAppearance
- Show:布尔值,决定该实例是否显示,任何实例都有这条属性。
下面的例子展示如何更改几何实例的颜色:
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var 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'
});
var primitive = new Cesium.Primitive({
geometryInstances : circleInstance,
appearance : new Cesium.PerInstanceColorAppearance({
translucent : false, //半透明
closed : true
})
});
scene.primitives.add(primitive);
setInterval(function() {
var attributes = primitive.getGeometryInstanceAttributes('circle');
attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.fromRandom({alpha : 1.0}));
},2000);
图形的颜色会不断改变。
外观
几何形状定义了结构。图元的另一个关键是外观,定义了图元的底纹、颜色等,比如一个单独的像素点是如何被上色的。一个图元可以有很多几何实例,但是只能有一个外观。根据外观的类型,外观将拥有一个material
属性来定义着色主体。
上图展示了图元、几何实例和外观之间的关系。一个图元可以对应着多个几何实例,但只能对应一个外观。
CesiumJS拥有以下外观:
MaterialAppearance
:一种适用于所有几何类型的外观,并支持material
来描述阴影。EllipsoidSurface
:MaterialAppearance
的一个版本,它假设几何形状与地球表面平行,比如一个多边形,并使用这个假设通过程序计算许多顶点属性来节省内存。PerInstanceColorAppearance
:使用每个实例的颜色为每个实例着色。PolylineMaterialAppearance
:支持改变多段线的材质PolylineColorAppearance
:使用每顶点或每段着色来为折线着色。
外观定义了当图元被绘制时在GPU上执行的完整GLSL顶点和片段着色器。外观还定义了完整的渲染状态,它控制着绘制原语时GPU的状态。我们可以直接定义呈现状态,也可以使用更高级的属性,比如closed
和translucent
,外观会将它们转换成渲染状态。
一旦外观被创建了,我们就不能改变它的renderState
属性,但是我们可以改变它的material
属性。我们也可以改变图元的appearance
属性。
大多数外观拥有flat
和faceForward
属性,它们间接控制着GLSL着色器。
flat
:扁平阴影,不考虑光线的因素。faceForward
:考虑光线影响,使它总是朝着观察者,避开背面的黑色区域,比如墙的内侧。
flat:true
faceForward: false
faceForward: true
几何形状和外观的兼容性
并不是所有的外观都适用于所有的几何形状。例如,椭球表面外观不适用于墙壁几何,因为墙壁不是在地球表面上。
要使外观与几何图形兼容,它们必须具有匹配的顶点格式,这意味着几何图形必须具有外观期望作为输入的数据。创建几何图形时可以提供VertexFormat。
一个几何形状的vertexFormat
决定了它是否可以和其他几何形状结合。两个几何形状不必具有相同的类型,但它们必须匹配vertex format。
为了方便,外观也拥有vertexFormat
属性或者是一个静态常量VERTEX_FORMAT
,这个常量可以作为几何图形的一个选项传入。