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


參考資料:

1、切片規則

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

如果瓦片集的URL是如下形式:

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

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

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

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

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

否則,某些服務器可能會返回與此處描述的不同的瓦片數據。

2、瓦片格式分析

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

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

2.1、數據頭部

該文件的第一部分是具有以下格式的數據頭。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每個渲染幀將進行數百次測試,以測試地形圖塊的可見性。 不過,這是一項重要的考驗。 在上圖中的配置中,覆蓋整個地球的地形瓦片位於視錐體內。 然而,其中一半以上是低於地平線而不需要渲染。

2、頂點數據

頭部數據后面緊跟着頂點數據,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.3、索引數據

緊跟在頂點數據之后的是索引數據。指數指定頂點如何鏈接在一起成三角形。如果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.4、擴展數據

隨后可以使用擴展數據來補充具有附加信息的量化網格。 每個擴展都以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'
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM