原文地址:https://cesiumjs.org/tutorials/Geometry-and-Appearances/
幾何體和外觀效果(Geometry and Appearances)
這篇教程會教大家學習Primitive API中支持的幾何體和外觀效果。這篇教程並不是面向Cesium的普通用戶,主要討論Cesium的高級知識,包括自定義三角網(mesh),形狀(shape),體(volume)以及他們的外觀。如果你是初學者,建議先學下這篇教程。
Cesium可以使用Entity創建不同的幾何體,比如多邊形和橢圓等。比如把下面代碼拷貝到 Sandcastle 的Hello World 就能創建一個帶條紋狀材質的矩形:
var 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 }) } });

這篇教程里,我們深入到圖元內部,使用
Geometry
類和
Appearance
類來創建效果。幾何體定義了圖元的結構,比如三角網、線、點等。外觀(appearance)定義了圖片的着色效果,包含完整的頂點(vertex)和片段(fragment)着色器(shader)以及着色器狀態。
Cesium支持下列幾何體:


BoxOutlineGeometry
A box




CorridorOutlineGeometry
以米為單位的折線 和 一個擠壓高度


CylinderOutlineGeometry
圓柱, 椎體,半椎體


EllipseOutlineGeometry
橢圓或者垂直擠壓的橢圓




RectangleOutlineGeometry
矩形或者垂直擠壓矩形


PolygonOutlineGeometry
多邊形,支持帶洞以及垂直擠壓


SimplePolylineGeometry
像素寬度定義的折線段


PolylineVolumeOutlineGeometry
一個二維圖形沿着折線的延伸體。




WallOutlineGeometry
垂直於地表的牆面

使用幾何體和外觀的優勢:
-
性能 - 尤其是繪制大量靜態圖元(比如整個美國的郵政編碼區域多邊形),使用幾何體可以把他們組合成一個單一的幾何體,這樣會減少cpu的開銷,並且充分利用GPU的能力。組合幾何體可以在web worker中完成,不會影響用戶界面的響應。
-
靈活性 - 圖元由幾何體和外觀構成。不過他們可以單獨修改。新建的幾何體可以兼容多種不同的外觀,反之亦然。
-
底層訪問 - 外觀提供了近乎最底層的渲染訪問,但是又不需要直接擔心渲染
Renderer
的細節技術 。外觀使下面的技術簡單了很多:- 編寫完整的頂點和片段着色器GLSL代碼。
- 使用用戶自定義的渲染狀態。
當然也有一些缺點:
- 使用幾何體和外觀需要寫更多的代碼,並且需要對圖形知識有深刻的理解。Entity是應用層的抽象;而幾何體和外觀更像是一個傳統3D引擎的級別。
- 對於靜態數據,幾何體合並非常有效,但是對於動態數據不適合。
使用幾何體和外觀來重新編寫示例代碼:
var viewer = new Cesium.Viewer('cesiumContainer'); var scene = viewer.scene; // 原始代碼 //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') }) }));
沒有用矩形的entity,我們使用了普通的 Primitive
, 它里面連接和幾何體和外觀。現在先忽略 Geometry
和 a GeometryInstance
的區別,只需知道instance是geometry的容器。
創建矩形幾何體 RectangleGeometry
的時候,這個矩形區域的三角網會貼合地球曲率。

因為我們預先知道這個幾何體是在球面上,所以直接使用 EllipsoidSurfaceAppearance
。這樣做也能節省內存 ,支持所有的材質,因為幾何體是在橢球體上方的固定高度(譯者注:個人理解是說頂點可以只需要二維坐標,高度值可以當作uniform傳進去)。
幾何體合並
當使用一個圖元去繪制多個靜態幾何體的時候,會有些效率提升。比如我們畫兩個矩形:
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') }) }));

創建了另一個矩形的instance,然后把兩個instance都添加到一個圖元里,使用同一個外觀去繪制。 一些外觀允許為每個instance設置不同的屬性(attribute)。比如,使用 PerInstanceColorAppearance
對每個instance着不同顏色。
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() }));

每個intance有一個Color
屬性。圖元里創建一個PerInstanceColorAppearance
,它知道使用每個instance的color屬性去着色。
幾何體合並允許Cesium高效的渲染大量幾何體。下面示例繪制了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() }));

拾取
當instance合並之后,仍然支持獨立訪問。通常,我們會設置一個id
屬性, Scene.pick
函數里通過它來判定哪個instance被拾取。這個id
可以任何js類型:字符串,數字,帶屬性的對象等等。
下面的示例創建一個帶id
的instance,當它被點擊的時候控制台會輸出一個消息。
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
而不是用instance對象本身去判定,主要是為了避免在創建圖元之后,我們的圖元甚至我們的項目對所有的instance對象 以及 它的幾何體 一直被引用無法釋放內存。因為幾何體一般包含了一個比較大的數組,這種方式就可以幫我們節省大量內存。
幾何體intances
目前為止,我們創建的每個幾何體instance都只包含一個幾何體。此外,instance竟然用來把同一個幾何體放置在場景的不同位置,包括不同大小和方向。由於多個instance可以引用同一個幾何體( Geometry
),而每個instance可以有不同的偏移矩陣(modelMatrix)。這樣,我們就只需要計算一次幾何體(計算頂點等)而多次使用它。

下面的代碼創建了一個EllipsoidGeometry
和 兩個instance. 每個instance 引用了相同的橢球幾何體,但是使用 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 }) }));

更新每個instance的屬性
即便是已經添加到圖元里,每個instance的一些屬性也可以修改,包括:
- Color :
ColorGeometryInstanceAttribute
決定了幾何體顏色。不過圖元應該設置一個PerInstanceColorAppearance
外觀。 - Show :布爾變量決定instance是否可見,對任意instance都有效。
下面代碼演示如何修改幾何體instance的顏色:
This example shows how to change the color of the geometry instance:
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);
幾何體的屬性需要通過 primitive.getGeometryInstanceAttributes
來獲取到。attributes
里的值可以直接修改。這里,我們每2秒鍾設置'circle'這個幾何體隨機顏色。
外觀(Appearances)
幾何體定義了結構。圖元的另一個關鍵屬性是appearance
,決定圖元的着色,也就說每個像素是如何上色的。一個圖元可以有若干個幾何體instance,但是只能有一個appearance屬性。根據appearance類型不同,一個appearance可能有一個 material
屬性,材質屬性決定了大體的着色( the bulk of the shading)。

Cesium 包含下述外觀類型:

MaterialAppearance
所有幾何體都使用同一個外觀,支持使用 materials 去定義着色效果.

EllipsoidSurface
MaterialAppearance
的簡化版本,假定幾何體都和地球橢球體平行,就像多邊形一樣。使用這個可以在計算大量頂點屬性的時候節省內存

PerInstanceColorAppearance
每個instance使用不同的顏色去着色。

PolylineMaterialAppearance
支持在折線上設置材質。

PolylineColorAppearance
支持折線在每個頂點或者每一段設置顏色。
外觀完整的定義了頂點和片段着色器代碼,在GPU中圖元渲染的時候使用。除非要自定義外觀,否則我們很少使用它們。外觀也定義了完整的渲染你狀態,它控制了圖元渲染時候的GPU狀態。我們可以使用高級的屬性來定義渲染狀態,比如 閉合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
屬性,但是我們能修改它的材質 material
。當然,我們可以整個替換圖元的appearance
屬性。
大部分外觀包含 flat
和faceForward
屬性, 這個直接控制了GLSL的着色效果。
flat
- 純色着色,不考慮光照效果。-
faceForward
- 當有光照的的時候,當視圖正對它的時候反轉法向量,避免牆體的背面是黑色的。
flat : true | faceForward : false | faceForward : true |



幾何體和外觀的匹配性
我們發現不是所有的外觀都能作用在任意幾何體上。比如EllipsoidSurfaceAppearance
不能用在WallGeometry
上,因為牆永遠垂直地表,而不是平行地表。
隱含之意,一個外觀能和一個幾何體匹配,需要頂點格式匹配,也就是說幾何體必須包含外觀需要的頂點格式數據。創建一個幾何體的時候,可以指定一個 VertexFormat
參數。
有時候為了簡化問題,但是接受一點點浪費和效率低,可以計算一個幾何體的所有頂點屬性格式,這樣就能和所有外觀兼容(忽略per-instance屬性)
var geometry = new Cesium.RectangleGeometry({ vertexFormat : Cesium.VertexFormat.ALL // ... });


如果使用EllipsoidSurfaceAppearance
,比如我們只創建了頂點的位置屬性,那么就會崩潰(get away)。
var geometry = new Ceisum.RectangleGeometry({ vertexFormat : Ceisum.VertexFormat.POSITION_ONLY // ... });
通常,我們怎么知道某種外觀需要哪種頂點格式?大部分外觀都有一個 vertexFormat
屬性, 甚至一個 VERTEX_FORMAT
靜態常量。
var geometry = new Ceisum.RectangleGeometry({ vertexFormat : Ceisum.EllipsoidSurfaceAppearance.VERTEX_FORMAT // ... }); var geometry2 = new Ceisum.RectangleGeometry({ vertexFormat : Ceisum.PerInstanceColorAppearance.VERTEX_FORMAT // ... }); var appearance = new Ceisum.MaterialAppearance(/* ... */); var geometry3 = new Ceisum.RectangleGeometry({ vertexFormat : appearance.vertexFormat // ... });
同樣,幾何體的 vertexFormat
屬性也決定了幾何體是否可以合並。如果要合並,可以幾何體類型不同,但是必須保證頂點格式一致。
相關資源
用戶手冊:
想了解材質的更多內容,請訪問Fabric。
想了解這塊的開發計划,請訪問: Geometry and Appearances Roadmap.
