cesium地形瓦片(Quantized-mesh)格式

參考資料:git

一、切片規則

量化網格-1.0格式的地形圖瓦片的切分規則和HeightMap的同樣,也是Tile Map Service (TMS)global-geodetic規則,詳情可見cesium地形瓦片(HeightMap)格式中的描述。github

若是瓦片集的URL是以下形式:web

http://assets.agi.com/stk-terrain/world/tiles

則金字塔根部兩個瓦片文件的URL:json

再下一級的8個瓦片文件的URL:數組

請求瓦片時,請確保在請求中包含如下HTTP標頭:bash

Accept: application/vnd.quantized-mesh,application/octet-stream;q=0.9

不然,某些服務器可能會返回與此處描述的不一樣的瓦片數據。服務器

二、瓦片格式分析

每一個圖塊是一個特殊編碼的三角形網格,其中頂點與圖塊邊緣處的相鄰網格重疊。換句話說,在根部,西部瓦片中最東部的頂點與東部瓦片中最西部的頂點具備相同的經度app

地形瓦片是gzip壓縮的。解壓縮後,tile是小端序(little-endian)的二進制數據。ide

2.一、數據頭部

該文件的第一部分是具備如下格式的數據頭。doubleIEEE 754 64位浮點數floatIEEE 754 32位浮點數

struct QuantizedMeshHeader
{
    // 瓦片中心在地心座標系下的座標
    double CenterX;
    double CenterY;
    double CenterZ;

    // 該瓦片覆蓋區域的最小和最大高度值
    // 最小值能夠低於全部頂點,最大值也能夠高於任何頂點
    // 由於在網格簡化(simplificatipn)的過程當中可能會有移除最小或最大頂點的狀況
    // 可是這些是適用於用於分析或可視化的的值
    float MinimumHeight;
    float MaximumHeight;

    // 瓦片的球面邊界. 
    // X,Y,Z 座標是地心座標系下的座標, 半徑的單位爲米
    double BoundingSphereCenterX;
    double BoundingSphereCenterY;
    double BoundingSphereCenterZ;
    double BoundingSphereRadius;

    // 地平線遮擋點,以橢球體縮放的地心座標系表示
    // 若是此點低於地平線,則整個圖塊位於地平線下方。
    // 有關更多信息,請參見http://cesiumjs.org/2013/04/25/Horizon-culling/。
    double HorizonOcclusionPointX;
    double HorizonOcclusionPointY;
    double HorizonOcclusionPointZ;
};

HorizonCullingOverview

在上圖中,綠色點對觀察者可見, 紅點不可見,由於它們位於視錐體以外,表示爲粗白線。 藍點位於視錐體內,但觀察者看不到它,由於它被地球遮擋。 換句話說,它低於地平線。 地平線剔除是一種直截了當的想法,即從當前觀察者位置看,您不須要渲染位於地平線下方的物體。 聽起來很簡單,細節變得棘手,特別是由於它須要很是快。cesium每一個渲染幀將進行數百次測試,以測試地形圖塊的可見性。 不過,這是一項重要的考驗。 在上圖中的配置中,覆蓋整個地球的地形瓦片位於視錐體內。 然而,其中一半以上是低於地平線而不須要渲染。

二、頂點數據

頭部數據後面緊跟着頂點數據,unsigned int是32位無符號整數,unsigned short是16位無符號整數。

struct VertexData
{
    unsigned int vertexCount;           // 頂點個數
    unsigned short u[vertexCount];      // 頂點橫座標
    unsigned short v[vertexCount];      // 頂點縱座標
    unsigned short height[vertexCount]; // 頂點高程值
};

vertexCount字段指示後面三個數組的大小。 這三個數組包含來自前一個值的增量,而後進行zig-zag編碼,以便使小整數(不管其符號如何)使用較少比特位。

解碼值的過程很簡單:

var u = 0;
var v = 0;
var height = 0;

// zig-zag 編碼
function zigZagEncode (value) {
  return (value >> 31) ^ (value << 1);
}
// zig-zag 解碼
function zigZagDecode(value) {
    return (value >> 1) ^ (-(value & 1));
}

for (i = 0; i < vertexCount; ++i) {
    u += zigZagDecode(uBuffer[i]);
    v += zigZagDecode(vBuffer[i]);
    height += zigZagDecode(heightBuffer[i]);

    uBuffer[i] = u;
    vBuffer[i] = v;
    heightBuffer[i] = height;
}

解碼後,每一個數組中值的含義以下:

數組 含義
u 圖塊中頂點的水平座標。 當u值爲0時,頂點位於圖塊的西邊緣。 當值爲32767時,頂點位於圖塊的東邊緣。 對於其餘值,頂點的經度是在圖塊的西邊和東邊的經度之間的線性插值
即:經度= 最西 + (u/32767) * (最東-最西)
v 圖塊中頂點的水平座標。 當u值爲0時,頂點位於圖塊的南邊緣。 當值爲32767時,頂點位於圖塊的北邊緣。 對於其餘值,頂點的緯度是在圖塊的南邊和北邊的經度之間的線性插值
即:緯度= 最南 + (v/32767) * (最北-最南)
height 圖塊中頂點的高度。 當高度值爲0時,頂點的高度等於圖塊內最小高度,如圖塊標題中指定的那樣。 當值爲32767時,頂點的高度等於圖塊內的最大高度。 對於其餘值,頂點的高度是最小和最大高度之間的線性插值。
即:高度= 最低 + (h/32767) * (最高-最低)

2.三、索引數據

緊跟在頂點數據以後的是索引數據。指數指定頂點如何連接在一塊兒成三角形。若是tile具備超過65536個頂點,則tile使用IndexData32結構對索引進行編碼。不然,它使用IndexData16結構。

爲了對索引數據強制進行字節對齊,在IndexData以前添加填充字節,以確保IndexData16爲2字節對齊IndexData32爲4字節對齊

struct IndexData16
{
    unsigned int triangleCount;                // 三角形個數
    unsigned short indices[triangleCount * 3]; // 三角形頂點索引
}

struct IndexData32
{
    unsigned int triangleCount;
    unsigned int indices[triangleCount * 3];
}

索引使用來自 webgl-loader 的 高水位標記(high water mark)編碼進行編碼。

索引解碼以下:

var highest = 0;
for (var i = 0; i < indices.length; ++i) {
    var code = indices[i];
    indices[i] = highest - code;
    if (code === 0) {
        ++highest;
    }
}

索引的每一個三元組以逆時針順序指定要渲染的一個三角形。

「High watermark encoding」

I really like this idea. Previously I’d been using simple delta encoding on the resulting index lists; that works, but the problem with delta coding is that a single outlier will produce two large steps – one to go from the current region to the outlier, then another one to get back. The high watermark scheme is almost as straightforward as straight delta coding and avoids this case completely.

Now, if you have an index list straight out of vertex cache optimization and vertex renumbering, the idea works as described. However, with the hybrid tri/paired-tri encoding I described last time, we have to be a bit more careful. While the original index list will indeed have each index be at most 1 larger than the highest index we’ve seen so far, our use of 「A ≥ B」 to encode whether the next set of indices describes a single triangle or a pair means that we might end up having to start from the second or third vertex of a triangle, and consequently see a larger jump than just 1. Luckily, the fix for this is simple – rather than keeping the high watermark always 1 higher than the largest vertex index we’ve seen so far, we keep it N higher where N is the largest possible 「step」 we can have in the index list. With that, the transform is really easy, so I’m just going to post my code in full:

static void watermark_transform(std::vector<int>& out_inds,
   const std::vector<int>& in_inds, int max_step)
{
   int hi = max_step - 1; // high watermark
   out_inds.clear();
   out_inds.reserve(in_inds.size());
   for (int v : in_inds)
   {
       assert(v <= hi);
       out_inds.push_back(hi - v);
       hi = std::max(hi, v + max_step);
   }
}

and the inverse is exactly the same, with the push_back in the middle replaced by the two lines

v = hi - v;
out_inds.push_back(v);

So what’s the value of N (aka max_step in the code), the largest step that a new index can be from the highest index we’ve seen so far? Well, for the encoding described last time, it turns out to be 3:

  • When encoding a single triangle, the worst case is a triangle with all-new verts. Suppose the highest index we’ve seen so far is k, and the next triangle has indices (k+1,k+2,k+3). Our encoding for single triangles requires that the first index be larger than the second one, so we would send this triangle as (k+3,k+1,k+2). That’s a step of 3.
  • For a pair of triangles, we get 4 new indices. So it might seem like we might get a worst-case step of 4. However, we know that the two triangles share an edge; and for that to be the case, the shared edge must have been present in the first triangle. Furthermore, we require that the smaller of the two indices be sent first (that’s what flags this as a paired tri). So the worst cases we can have for the first two indices are (k+2,k+3) and (k+1,k+3), both of which have a largest step size of 2. After the first two indices, we only have another two indices to send; worst-case, they are both new, and the third index is larger than the fourth. This corresponds to a step size of 2. All other configurations have step sizes ≤1.

三角索引以後還有四個(邊緣)索引列表,這些索引列表保存了tile全部邊緣上的頂點。 知道哪些頂點在邊緣上以添加裙邊以隱藏相鄰細節層之間的裂縫是有幫助的。

struct EdgeIndices16
{
    unsigned int westVertexCount;
    unsigned short westIndices[westVertexCount];

    unsigned int southVertexCount;
    unsigned short southIndices[southVertexCount];

    unsigned int eastVertexCount;
    unsigned short eastIndices[eastVertexCount];

    unsigned int northVertexCount;
    unsigned short northIndices[northVertexCount];
}

struct EdgeIndices32
{
    unsigned int westVertexCount;
    unsigned int westIndices[westVertexCount];

    unsigned int southVertexCount;
    unsigned int southIndices[southVertexCount];

    unsigned int eastVertexCount;
    unsigned int eastIndices[eastVertexCount];

    unsigned int northVertexCount;
    unsigned int northIndices[northVertexCount];
}

2.四、擴展數據

隨後可使用擴展數據來補充具備附加信息的量化網格。 每一個擴展都以ExtensionHeader結構開頭,包含惟一標識符和擴展數據的大小(以字節爲單位)。 unsigned char是一個8位無符號整數。

struct ExtensionHeader
{
    unsigned char extensionId;     // 擴展ID
    unsigned int  extensionLength; // 擴展長度
}

在定義新擴展時,將爲它們分配惟一標識符。 若是沒有爲tileset定義擴展,則ExtensionanEeader將不包含在quanitzed-mesh中。 能夠將多個擴展附加到量化網格數據,其中每一個擴展的排序由服務器肯定。

客戶端能夠經過使用-分隔擴展名來請求多個擴展。 例如,客戶端可使用如下Accept標頭請求頂點法線watermask

Accept : 'application/vnd.quantized-mesh;extensions=octvertexnormals-watermask'

能夠爲量化網格定義如下擴展:

地形光照(Terrain Lighting)

  • 名稱(Name):Oct-Encoded Per-Vertex Normals

  • 標識(id): 1

  • 描述(Description):將每一個頂點光照屬性添加到量化網格。 每一個頂點法線使用oct編碼將傳統的x,y,z 96位浮點單位向量壓縮爲x,y 16位表示。 oct編碼在介紹在」A Survey of Efficient Representations of Independent Unit Vectors「, Cigolle et al 2014: http://jcgt.org/published/0003/02/01/

  • 數據定義(Data Definition):

    struct OctEncodedVertexNormals
    {
        unsigned char xy[vertexCount * 2];
    }
  • 請求(Requesting):對於要包含在量化網格中的oct編碼的每頂點法線,客戶端必須使用如下HTTP標頭請求此擴展:

    Accept : 'application/vnd.quantized-mesh;extensions=octvertexnormals'
  • 附註(Comments):使用擴展名vertexnormals請求此擴展的原始實現。 不推薦使用vertexnormals的擴展標識符,而且實現如今必須經過在請求標頭擴展參數中添加octvertexnormal來請求頂點法線,如上所示。

水面掩碼(Water Mask)

  • 名稱(Name):Water Mask

  • 標識(id): 2

  • 描述(Description):添加用於渲染水效果的海岸線數據。若是圖塊區域所有是水面或者陸地,則爲1字節,不然是256*256*1=65536字節。掩碼值0表示陸地,255表示水面。掩碼中的值是從西向東、從北到南定義的,第一個字節是西北角的watermask值。容許0-255之間的值,以便支持海岸線的抗鋸齒。

  • 數據定義(Data Definition):

    徹底由陸地或水覆蓋的地形瓦片由單個字節定義。

    struct WaterMask
    {
        unsigned char mask;
    }

    包含陸地和水混合的地形瓦片定義了256 x 256高度值網格。

    struct WaterMask
    {
        unsigned char mask[256 * 256];
    }
  • 請求(Requesting):要使watermask包含在量化網格中,客戶端必須使用如下HTTP標頭請求此擴展:

    Accept : 'application/vnd.quantized-mesh;extensions=watermask'

元數據(Metadata)

  • 名稱(Name):Metadata

  • 標識(id): 4

  • 描述(Description):向每一個瓦片添加一個JSON對象,能夠存儲有關瓦片的額外信息。 潛在用途包括存儲瓦片中的土地類型(例如森林,沙漠等)或子級瓦片的可用性。

  • 數據定義(Data Definition):

    struct Metadata
    {
        unsigned int jsonLength;
        char json[jsonLength];
    }

    包含陸地和水混合的地形瓦片定義了256 x 256高度值網格。

    struct WaterMask
    {
        unsigned char mask[256 * 256];
    }
  • 請求(Requesting):要使metadata包含在量化網格中,客戶端必須使用如下HTTP標頭請求此擴展:

    Accept : 'application/vnd.quantized-mesh;extensions=metadata'
相關文章
相關標籤/搜索