翻譯自官方文檔。
這篇教程介紹的是 Primitive API 有關的資料,適合高級用戶。想快速繪制各種形狀,建議參考 Entity API。
1. 幾何概述
Cesium 可以創建 Entity API 創建各種幾何圖形,例如繪制一個矩形:
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
})
}
});

這是一個很常見的例子。
在本教程中,將深入研究 Primitive API:使用 Geometry 和 Appearance 來構造幾何圖形。
使用 Primitive API 中的 Geometry 和 Appearance 的好處是:
- 性能:當需要繪制大量靜態圖形時,使用 Primitive API 可以將圖形組合成一個大圖形,減少 CPU 的開銷,更充分利用 GPU;使用 WebWorker 性能更佳。
- 靈活性:Primitive 主要是由 Geometry 和 Appearance 構成的。解耦它們,就可以分別修改它們。
- 更底層:Appearance 提供了接近底層的編程風格,但是你又不需要考慮 WebGL 的細節,可以輕松達到:
- 編寫完整的 GLSL 頂點、片元着色器
- 自定義渲染
缺點也是有的:
- 需要更復雜的代碼,也需要額外的圖形學知識。Primitive API 更接近傳統三維引擎的編程。
- 組合圖形對動態數據不一定有效。
使用 Primitive API 重寫上面的代碼:
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')
})
}));
GeometryInstance 是 Geometry 的容器,當前只有一個 Geometry。
為了創建矩形,使用 RectangleGeometry:

因為它是地面的矩形,所以使用 EllipsoidSurfaceAppearance 這種外觀,
2. 幾何有哪些種類
十幾種(有對應的線框模式),簡單列舉如下:

在沙盒中可以找到對應的示例代碼(Geometries And Appearance):

3. 組合幾何形狀
當你需要繪制多個靜態的幾何圖形時,Primitive API 就有性能優勢了。例如,可以在一個 Primitive 中組合兩個矩形:
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')
})
}));

創建了兩個 geometry instance,共用了一個 appearance。
有一些 Appearance 允許使用 GeometryInstance 自己的屬性來着色:
const instance = new Cesium.GeometryInstance({
geometry : // 和上面一樣
attributes : {
color : new Cesium.ColorGeometryInstanceAttribute(0.0, 0.0, 1.0, 0.8)
}
});
const anotherInstance = new Cesium.GeometryInstance({
geometry : // 和上面一樣
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()
}));
出來的效果:

每個 GeometryInstance 都帶了一個 color attribute。
組合圖形讓 Cesium 能繪制大量幾何圖形,下例是 2592 個獨立顏色的矩形:
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()
}));

4. 點擊拾取
GeometryInstance 繪制后仍然是可以獨立訪問的,只需分配一個 id 即可。
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
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);
// pick.id 即 GeometryInstance.id
if (Cesium.defined(pick) && (pick.id === 'my rectangle')) {
console.log('Mouse clicked rectangle.');
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
5. 幾何實例:instance 技術
Geometry 允許使用不同的轉換矩陣,構造不同位置的 GeometryInstance,這樣頂點數據只存了一份:

下面這個實例只創建一個橢球幾何體,但是用不同的矩陣來表示不同的位置。
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
})
}));

6. 更新幾何實例的屬性
繪制幾何完成后,可以更新 GeometryInstance 的屬性,包括:
- Color:確定 GeometryInstance 顏色的
ColorGeometryInstanceAttribute對象,使用此屬性,Primitive 必須使用的是PerInstanceColorAppearance - Show:布爾值,表達 GeometryInstance 是否可視。
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(function() {
var attributes = primitive.getGeometryInstanceAttributes('circle');
attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.fromRandom({alpha : 1.0}));
},2000);
這個例子每隔2秒,顏色就會變化一次。
使用 primitive.getGeometryInstanceAttributes('id') 可以獲取某個 GeometryInstance 的屬性,當然,它的返回值的屬性可以直接修改,像代碼中的 attributes.color = ... 一樣。
7. 幾何的外觀
幾何只定義了結構,外觀負責顏值。Primitive 可以有若干圖幾何,但是只能有一種外觀。
Cesium 自帶的外觀如下:
- MaterialAppearance:適用於任何幾何的外觀,支持描述陰影的材質
- EllipsoidSurface:MaterialAppearance 的另一個版本,它假設幾何形狀是貼地的,這樣計算頂點屬性的時候可以節約一些內存
- PerInstanceColorAppearance:每個 GeometryInstance 的顏色是獨立控制的外觀
- PolylineMaterialAppearance:支持對折線進行着色
- PolylingColorAppearance:可以控制折線的點或者線段的顏色進行着色的外觀
可以定義渲染狀態,或者使用更高級的屬性 closed、translucent,例如:
// 不透明
var appearance = new Cesium.PerInstanceColorAppearance({
translucent : false,
closed : true
});
// 這是比較底層的寫法,效果與上面一樣
var anotherAppearance = new Cesium.PerInstanceColorAppearance({
renderState : {
depthTest : {
enabled : true
},
cull : {
enabled : true,
face : Cesium.CullFace.BACK
}
}
});
創建外觀后,是沒辦法改變它的 renderState 的,但是可以修改材質。
可以更改圖元的外觀屬性,大多數外觀有 flat 和 faceForward 屬性,這兩個屬性間接控制着 GLSL 着色器。
- flat - 不考慮照明陰影的平面着色
- faceForward:當照明一個圖元時,反轉三角面的法線(也就是頂點法線),使背面看起來像正面,比如牆的內面着色。

8. 幾何與外觀的兼容性
並不是所有的外觀都適合所有的幾何。比如,EllipsoidSurfaceAppearance 不適合 WallGeometry,因為牆面是垂直地面的,並不貼地。
為了使得外觀與幾何圖形兼容,它們的頂點格式必須匹配。指定 Geometry 的 vertexFormat 屬性即可。
下圖可以說明這個問題,當外觀需要有 st(即紋理坐標)而 Geometry 沒有時,就會報錯:

外觀類有 VertexFormat 靜態屬性,或外觀對象有 vertexFormat 屬性可供使用。
幾何的 vertexFormat 是組合幾何圖形的因素,幾何的類型可以不同,但是頂點的格式必須是一樣的。
const geometry = new Ceisum.RectangleGeometry({
vertexFormat : Ceisum.EllipsoidSurfaceAppearance.VERTEX_FORMAT
// ...
});
const geometry2 = new Ceisum.RectangleGeometry({
vertexFormat : Ceisum.PerInstanceColorAppearance.VERTEX_FORMAT
// ...
});
const appearance = new Ceisum.MaterialAppearance(/* ... */);
const geometry3 = new Ceisum.RectangleGeometry({
vertexFormat : appearance.vertexFormat
// ...
});
譯者注:頂點格式,即頂點的坐標、法線、uv(即st)、顏色等組合情況
9. 個人補充 Entity API vs Primitive API
Entity 用的是 Graphic,參數化創建圖形,並可以參數符號化,幾何形狀和外觀耦合在一起
Primitive 用的是 Geometry + Appearance,可以分別修改幾何形狀和外觀。雖然有預定義的 Geometry,但是 Primitive API 提供的是更接近 WebGL 的接口,構造 Geometry 完全可以使用與 WebGL 十分接近的邏輯,傳入頂點、法線等素材創建難以想象的形狀。
Entity 擁有 Property 進行時間插值,Primitive 沒有時間插值,需要深入着色器。
除此之外,Entity 在數據量特別大的情況下性能比 Primitive 差。
