Cesium中的圖形技術:Primitive API 高級

官方雖說文章已過期,且說 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);

② 使用 Primitive API 繪製一個四面體

這個四面體的幾何中心做爲局部座標原點。爲了讓它得以可視化,須要計算一個模型矩陣,用以定位和放縮它。此外,由於它只有座標,且暫時只打算使用平面陰影,因此暫時不須要法線。

首先,構建源代碼並啓動本地 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

對幾何圖形的計算可使用 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() 方法同步生成四面體。

至此,自定義幾何圖形的生成教程結束。

相關文章
相關標籤/搜索