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

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


BoxOutlineGeometryA box




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


CylinderOutlineGeometry圓柱, 椎體,半椎體


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




RectangleOutlineGeometry矩形或者垂直擠壓矩形


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


SimplePolylineGeometry像素寬度定義的折線段


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




WallOutlineGeometry垂直于地表的墻面

使用幾何體和外觀的優(yōu)勢:
性能 - 尤其是繪制大量靜態(tài)圖元(比如整個美國的郵政編碼區(qū)域多邊形),使用幾何體可以把他們組合成一個單一的幾何體,這樣會減少cpu的開銷,并且充分利用GPU的能力。組合幾何體可以在web worker中完成,不會影響用戶界面的響應(yīng)。
靈活性 - 圖元由幾何體和外觀構(gòu)成。不過他們可以單獨修改。新建的幾何體可以兼容多種不同的外觀,反之亦然。
-
底層訪問 - 外觀提供了近乎最底層的渲染訪問,但是又不需要直接擔(dān)心渲染
Renderer的細節(jié)技術(shù) 。外觀使下面的技術(shù)簡單了很多:- 編寫完整的頂點和片段著色器GLSL代碼。
- 使用用戶自定義的渲染狀態(tài)。
當(dāng)然也有一些缺點:
- 使用幾何體和外觀需要寫更多的代碼,并且需要對圖形知識有深刻的理解。Entity是應(yīng)用層的抽象;而幾何體和外觀更像是一個傳統(tǒng)3D引擎的級別。
- 對于靜態(tài)數(shù)據(jù),幾何體合并非常有效,但是對于動態(tài)數(shù)據(jù)不適合。
使用幾何體和外觀來重新編寫示例代碼:
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, 它里面連接和幾何體和外觀?,F(xiàn)在先忽略 Geometry和 a GeometryInstance 的區(qū)別,只需知道instance是geometry的容器。
創(chuàng)建矩形幾何體 RectangleGeometry的時候,這個矩形區(qū)域的三角網(wǎng)會貼合地球曲率。

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

創(chuàng)建了另一個矩形的instance,然后把兩個instance都添加到一個圖元里,使用同一個外觀去繪制。 一些外觀允許為每個instance設(shè)置不同的屬性(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 屬性。圖元里創(chuàng)建一個PerInstanceColorAppearance,它知道使用每個instance的color屬性去著色。
幾何體合并允許Cesium高效的渲染大量幾何體。下面示例繪制了2592個不同顏色的矩形。優(yōu)化之后,渲染非常塊。
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()
}));

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

下面的代碼創(chuàng)建了一個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的屬性
即便是已經(jīng)添加到圖元里,每個instance的一些屬性也可以修改,包括:
- Color :
ColorGeometryInstanceAttribute決定了幾何體顏色。不過圖元應(yīng)該設(shè)置一個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秒鐘設(shè)置'circle'這個幾何體隨機顏色。
外觀(Appearances)
幾何體定義了結(jié)構(gòu)。圖元的另一個關(guān)鍵屬性是appearance,決定圖元的著色,也就說每個像素是如何上色的。一個圖元可以有若干個幾何體instance,但是只能有一個appearance屬性。根據(jù)appearance類型不同,一個appearance可能有一個 material 屬性,材質(zhì)屬性決定了大體的著色( the bulk of the shading)。

Cesium 包含下述外觀類型:

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

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

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

PolylineMaterialAppearance 支持在折線上設(shè)置材質(zhì)。

PolylineColorAppearance支持折線在每個頂點或者每一段設(shè)置顏色。
外觀完整的定義了頂點和片段著色器代碼,在GPU中圖元渲染的時候使用。除非要自定義外觀,否則我們很少使用它們。外觀也定義了完整的渲染你狀態(tài),它控制了圖元渲染時候的GPU狀態(tài)。我們可以使用高級的屬性來定義渲染狀態(tài),比如 閉合closed 和 半透明translucent,外觀會把他們轉(zhuǎn)換為真正的底層狀態(tài),比如:
// 一個不透明的盒子,視點永遠不會進到里面去
// 那么就需要啟用背面裁剪,深度檢測,不需要混合。
var appearance = new Cesium.PerInstanceColorAppearance({
translucent : false,
closed : true
});
// 這個和上面的設(shè)置等價
var anotherAppearance = new Cesium.PerInstanceColorAppearance({
renderState : {
depthTest : {
enabled : true
},
cull : {
enabled : true,
face : Cesium.CullFace.BACK
}
}
});
一旦我們的外觀創(chuàng)建了,我們不能修改它的renderState屬性,但是我們能修改它的材質(zhì) material。當(dāng)然,我們可以整個替換圖元的appearance屬性。
大部分外觀包含 flat 和faceForward 屬性, 這個直接控制了GLSL的著色效果。
-
flat- 純色著色,不考慮光照效果。 -
faceForward- 當(dāng)有光照的的時候,當(dāng)視圖正對它的時候反轉(zhuǎn)法向量,避免墻體的背面是黑色的。
flat : true | faceForward : false | faceForward : true |



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


如果使用EllipsoidSurfaceAppearance,比如我們只創(chuàng)建了頂點的位置屬性,那么就會崩潰(get away)。
var geometry = new Ceisum.RectangleGeometry({
vertexFormat : Ceisum.VertexFormat.POSITION_ONLY
// ...
});
通常,我們怎么知道某種外觀需要哪種頂點格式?大部分外觀都有一個 vertexFormat 屬性, 甚至一個 VERTEX_FORMAT靜態(tài)常量。
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 屬性也決定了幾何體是否可以合并。如果要合并,可以幾何體類型不同,但是必須保證頂點格式一致。
相關(guān)資源
用戶手冊:
想了解材質(zhì)的更多內(nèi)容,請訪問Fabric。
想了解這塊的開發(fā)計劃,請訪問: Geometry and Appearances Roadmap.
