官方雖說文章已過期,且說 2018 年會更新文章的代碼,可是咕咕咕到了如今都沒更新。git
Geometry and Appearances · CesiumGS/cesium Wiki (github.com)es6
Cesium支持許多常見的圖形,開箱即用。可是,有時候這些預置的圖形不必定能知足需求。github
因爲 幾何 和 外觀 在 Primitive API 中是分離的,因此能夠在外觀相同的時候添加幾何形狀,反之亦然。web
這麼作須要必定的圖形學知識,在這個教程中,將建立一個簡單的三棱錐。算法
若是你有更好的幾何圖形,能夠參考官方 「貢獻說明」 來提交你的成果。api
Cesium 中的 Geometry
支持索引三角形、未索引三角形、線框渲染和點渲染。開始,咱們先爲四面體(三棱錐)建立它的幾何形狀,即四個等邊三角形。首先,在 Source/Core/
下建立一個 TetrahedronGeometry.js
:數組
官方代碼是 AMD 格式的,我改成和 1.63 以後相似的風格。原文的代碼能夠直接到文章頂部的原文連接找。app
import Cartesian3 from './Cartesian3' import ComponentDatatype from './ComponentDatatype' import PrimitiveType from './PrimitiveType' import BoundingSphere from './BoundingSphere' import GeometryAttribute from './GeometryAttribute' import GeometryAttributes from './GeometryAttributes' import GeometryPipeline from './GeometryPipeline' import VertexFormat from './VertexFormat' import Geometry from './Geometry' function TetrahedronGeometry() { const negativeRootTwoOverThree = -Math.sqrt(2.0) / 3.0; const negativeOneThird = -1.0 / 3.0; const rootSixOverThree = Math.sqrt(6.0) / 3.0; const positions = new Float64Array(4 * 3); // 四面體有4個三角形,共計12個點,可是因爲重合的關係,能夠只記錄4個點 // 點0 座標 positions[0] = 0.0; positions[1] = 0.0; positions[2] = 1.0; // 點1 座標 positions[3] = 0.0; positions[4] = (2.0 * Math.sqrt(2.0)) / 3.0; positions[5] = negativeOneThird; // 點2 座標 positions[6] = -rootSixOverThree; positions[7] = negativeRootTwoOverThree; positions[8] = negativeOneThird; // 點3 座標 positions[9] = rootSixOverThree; positions[10] = negativeRootTwoOverThree; positions[11] = negativeOneThird; // 建立頂點屬性中的座標 const attributes = new GeometryAttributes({ position : new GeometryAttribute({ componentDatatype : ComponentDatatype.DOUBLE, componentsPerAttribute : 3, values : positions }) }); const indices = new Uint16Array(4 * 3); // 後面的三角形用到 0、一、2 號點座標 indices[0] = 0; indices[1] = 1; indices[2] = 2; // 左邊的三角形用到 0、二、3 號點座標 indices[3] = 0; indices[4] = 2; indices[5] = 3; // 右邊的三角形用到 0、三、1 號點座標 indices[6] = 0; indices[7] = 3; indices[8] = 1; // 下面的三角形用到 二、一、3 號點座標 indices[9] = 2; indices[10] = 1; indices[11] = 3; // 指定此四面體的各類屬性 this.attributes = attributes; this.indices = indices; this.primitiveType = PrimitiveType.TRIANGLES; this.boundingSphere = undefined; } export default TetrahedronGeometry
譯者注:很明顯,這是一個死數據的幾何形狀,不具有任何參數化建模(例如我能外部傳入數據)的能力,可是這做爲演示足夠了異步
四面體由四個頂點組成,其全部點都在半徑爲1的球面上,爲了表達精度,座標存儲在 Float64Array
這個類型數組中。下圖是示意圖(局部座標)函數
四面體的有三個三角形面,每一個三角形的點有三個,使用座標數組中的序號來表達,稱之爲索引值,來節約內存。
對於這個四面體,每一個頂點均被索引了三次(三個三角形共用一個頂點)。索引數組能夠存在 Uint16Array
中,也能夠存在 Uint32Array
中,若是頂點數量超過 64K 個,可考慮用後者。
「Back」 三角形這兒有個藍箭頭,表示的是頂點的順序,如今是逆時針。使用右手定則,能夠判斷它的這個三角形的「外面」是朝向屏幕的。Cesium 使用逆時針順序來定義三角形的面朝向。
四面體類須要分配四個屬性,這是 Geometry 必須的:
this.attributes = attributes; this.indices = indices; // 可選 this.primitiveType = Cesium.PrimitiveType.TRIANGLES; this.boundingSphere = undefined; // 可選
attributes
- 一個 GeometryAttributes
對象,每一個頂點屬性都存儲在這個對象中,頂點屬性包括但不限於:座標、法線、顏色值、uv等indices
- 索引數組,使用索引能夠減小座標個數,節約內存primitiveType
- 幾何圖元的類型,這裏使用了 Cesium.PrimitiveType.TRIANGLES
,即每三個頂點被解析爲一個三角形渲染。boundingSphere
- 可選參數,包圍整個幾何形狀的球體,能夠輔助剔除來提升性能。能夠經過計算 boundingSphere
來提升繪製四面體的性能。
this.boundingSphere = BoundingSphere.fromVertices(positions);
BoundingSphere 有一個 fromVertices()
函數來計算緊密包圍幾何形狀全部座標的包圍球。不過,一般在其餘狀況下,能夠用簡單的幾何知識來更快建立它。因爲預先知道這個四面體的頂點座標位於半徑爲1的球體上,因此能夠用半徑爲1的球面做爲邊界球:
this.boundingSphere = new BoundingSphere(new Cartesian3(0.0, 0.0, 0.0), 1.0);
這個四面體的幾何中心做爲局部座標原點。爲了讓它得以可視化,須要計算一個模型矩陣,用以定位和放縮它。此外,由於它只有座標,且暫時只打算使用平面陰影,因此暫時不須要法線。
首先,構建源代碼並啓動本地 Cesium 開發環境
譯者注:直接把上面這個類寫在沙盒中更快且省事,方便修改
隨後,打開沙盒,寫一些代碼:
const widget = new Cesium.CesiumWidget('cesiumContainer'); const scene = widget.scene; const ellipsoid = widget.centralBody.ellipsoid; // 看似複雜,其實只是對經緯度 (-100, 40) 這個點作垂直地表向上平移200km的計算,並將幾何體放大50w倍(即變成500km那麼大),返回矩陣而已 const modelMatrix = Cesium.Matrix4.multiplyByUniformScale( Cesium.Matrix4.multiplyByTranslation( Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian( Cesium.Cartographic.fromDegrees(-100.0, 40.0))), // e-n-u計算,返回局部到世界座標的轉換矩陣 new Cesium.Cartesian3(0.0, 0.0, 200000.0)), // 平移計算,矩陣·平移向量 500000.0); // 縮放計算,矩陣·50w const instance = new Cesium.GeometryInstance({ geometry : new Cesium.TetrahedronGeometry(), // 若是直接寫在代碼而不是構建出來的,能夠直接 new TetrahedronGeometry() modelMatrix : modelMatrix, attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.WHITE) // 快捷計算頂點顏色 } }); /* 使用 Primitive API 繪製幾何 */ scene.primitives.add(new Cesium.Primitive({ geometryInstances : instance, appearance : new Cesium.PerInstanceColorAppearance({ flat : true, translucent : false }) }));
而後,它大概會長這樣:
由於設置了 appearance 中的 flat: true
,沒有陰影,因此很難觀察三角面。若是想看到線框圖,能夠改 TetrahedronGeometry 對象的 primitiveType 爲 LINE。
使用 GeometryPipeline
類作這個轉換工做會比較方便。
GeometryPipeline.toWireframe
方法將幾何圖形轉換爲 primitiveType 爲 LINE 的模式。
稍做修改建立 GeometryInstance 的代碼:
const instance = new Cesium.GeometryInstance({ geometry : Cesium.GeometryPipeline.toWireframe(new Cesium.TetrahedronGeometry()), modelMatrix : modelMatrix, attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.WHITE) } });
如圖所示:
要想外觀看起來有陰影,Geometry 的頂點屬性中必須擁有法線。三角形的法線是垂直於三角面的單位向量:
在圖形學中,一般法線是指每一個頂點的法線。頂點的法線是這麼定義的:頂點的法線是全部與它有關的面的法線的加和,而且必須是單位向量。下圖所示:
修改 TetrahedronGeometry 類,使用 GeometryPipeline.computeNormal
方法能夠計算它的每一個頂點的法線:
// ... 上面省略 const boundingSphere = new BoundingSphere(new Cartesian3(0.0, 0.0, 0.0), 1.0); // 主要是改這裏 const geometry = GeometryPipeline.computeNormal(new Geometry({ attributes: attributes, indices: indices, primitiveType: PrimitiveType.TRIANGLES, boundingSphere: boundingSphere })); this.attributes = geometry.attributes; this.indices = geometry.indices; this.primitiveType = geometry.primitiveType; this.boundingSphere = geometry.boundingSphere;
繼續執行代碼,圖形就變成了這樣:
這並非想象中的樣子。
爲了更好地觀察爲何會這樣,不如把頂點的法線可視化出來,使用 createTangentSpaceDebugPrimitive
這個全局函數便可,修改沙盒中的代碼:
scene.primitives.add(Cesium.createTangentSpaceDebugPrimitive({ geometry: tetrahedron, modelMatrix: modelMatrix, length: 0.2 }));
你就會看到頂點的法線了:
能夠看到,這些頂點的法線由於是各個三角面的法線的矢量和,看起來就不是那麼」法線「了。
一般,一個頂點附近的面大體接近平行的話,用這些平面的法線矢量和做爲頂點的法線,會使得頂點附近的顏色看起來很光滑過渡。可是,像這種十分尖銳的形狀,就不該該使用索引式頂點了,而是每一個面的頂點單獨使用,每一個頂點的法線本身獨立。
因此爲了使得陰影看起來常規一些,必須使用獨立頂點的三角形,即四面體一共4個三角形,一共12個頂點。如圖:
再次修改 TetrahedronGeometry
類:
var positions = new Float64Array(4 * 3 * 3); // 4個三角形,每一個有3個座標,一個座標有3個float份量 // back triangle positions[0] = 0.0; positions[1] = 0.0; positions[2] = 1.0; positions[3] = 0.0; positions[4] = (2.0 * Math.sqrt(2.0)) / 3.0; positions[5] = negativeOneThird; positions[6] = -rootSixOverThree; positions[7] = negativeRootTwoOverThree; positions[8] = negativeOneThird; // left triangle positions[9] = 0.0; positions[10] = 0.0; positions[11] = 1.0; positions[12] = -rootSixOverThree; positions[13] = negativeRootTwoOverThree; positions[14] = negativeOneThird; positions[15] = rootSixOverThree; positions[16] = negativeRootTwoOverThree; positions[17] = negativeOneThird; // right triangle positions[18] = 0.0; positions[19] = 0.0; positions[20] = 1.0; positions[21] = rootSixOverThree; positions[22] = negativeRootTwoOverThree; positions[23] = negativeOneThird; positions[24] = 0.0; positions[25] = (2.0 * Math.sqrt(2.0)) / 3.0; positions[26] = negativeOneThird; // bottom triangle positions[27] = -rootSixOverThree; positions[28] = negativeRootTwoOverThree; positions[29] = negativeOneThird; positions[30] = 0.0; positions[31] = (2.0 * Math.sqrt(2.0)) / 3.0; positions[32] = negativeOneThird; positions[33] = rootSixOverThree; positions[34] = negativeRootTwoOverThree; positions[35] = negativeOneThird; var indices = new Uint16Array(4 * 3); // 12個頂點索引,各自獨立 // back triangle indices[0] = 0; indices[1] = 1; indices[2] = 2; // left triangle indices[3] = 3; indices[4] = 4; indices[5] = 5; // right triangle indices[6] = 6; indices[7] = 7; indices[8] = 8; // bottom triangle indices[9] = 9; indices[10] = 10; indices[11] = 11;
如今,仍舊使用 GeometryPipeline.computeNormal
來計算法線省的本身燒腦。最終,繪製的新四面體以下:
如今,每一個三角形的陰影看起來就像正常的了,而不是在頂點附近」生硬「地」光滑過渡「。也看到了頂點的法線,同一個三角形的三個頂點法線是跟面的法線平行的。
至此,繪製算結束了。
對幾何圖形的計算可使用 WebWorker 技術異步進行,使得界面保持響應。四面體可能比較簡單,計算量少,可是對於複雜的幾何體就很難說了,可使用 Cesium 內置的 WebWorker。
首先,你得在 Source/Workers
目錄下建立一個 createTetrahedronGeometry.js
文件,寫一個函數來建立 Geometry。
譯者注:已改成 es6 風格
import TetrahedronGeometry from '../Core/TetrahedronGeometry' // 這個看你喜愛咯 import PrimitivePipeline from '../Scene/PrimitivePipeline' import createTaskProcessWorker from './createTaskProcessWorker' // 使用 Cesium 內置的工具函數 function createTetrahedronGeometry(parameters, transferableObjects) { const geometry = TetrahedronGeometry.createGeometry(); PrimitivePipeline.transferGeometry(geometry, transferableObjects); return { geometry : geometry, index : parameters.index }; } export default createTaskProcessWorker(createTetrahedronGeometry)
此時,TetrahedronGeometry 類尚未 createGeometry() 方法,修改使其擁有:
TetrahedronGeometry.createGeometry = function() { return new Geometry({ attributes : attributes, indices : indices, primitiveType : PrimitiveType.TRIANGLES, boundingSphere : new BoundingSphere(new Cartesian3(0.0, 0.0, 0.0), 1.0) }); };
最後,加上一句代碼使得內置的 WebWorker 能夠識別:
this._workerName = 'createTetrahedronGeometry'; // this 指的是 TetrahedronGeometry
這些修改就能使用上面的代碼異步生成四面體了。固然,你依舊可使用 createGeometry() 方法同步生成四面體。
至此,自定義幾何圖形的生成教程結束。