参考资料:

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;
};

在上图中,绿色点对观察者可见, 红点不可见,因为它们位于视锥体之外,表示为粗白线。 蓝点位于视锥体内,但观察者看不到它,因为它被地球遮挡。 换句话说,它低于地平线。 地平线剔除是一种直截了当的想法,即从当前观察者位置看,您不需要渲染位于地平线下方的物体。 听起来很简单,细节变得棘手,特别是因为它需要非常快。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'
    

cesium地形瓦片(Quantized-mesh)格式的更多相关文章

  1. cesium地形瓦片(HeightMap)格式

    目录 1.瓦片切分规则 2..terrain瓦片格式分析 参考资料: heightmap 1.0 Tile Map Service Specification 国内主要地图瓦片坐标系定义及计算原理 H ...

  2. 还原堆栈信息,分析地形系统使用ASTC格式的纹理导致Crash的问题

    0x00 前言 在这篇文章中,我们选择了过去一周Unity官方社区交流群中比较有代表性的几个问题,总结在这里和大家进行分享.主要涵盖了IL2CPP.Scripting.Virtual Reality. ...

  3. WorldWind源码剖析系列:地形瓦片类TerrainTile和地形瓦片服务类TerrainTileService

    地形瓦片类TerrainTile 用来抽象封装用户漫游中所请求的地形瓦片数据类型. 地形瓦片服务类TerrainTileService提供了从BIL(Binary Interleaved by Lin ...

  4. Cesium中的地形和坐标转换说明

    1 Cesium中的地形 Cesium中的地形系统是一种由流式瓦片数据生成地形mesh的技术,厉害指出在于其可以自动模拟出地面.海洋的三维效果.创建地形图层的方式如下: var terrainProv ...

  5. Cesium加载地形数据只显示半个地球

    Cesium第0级地形包括两个瓦片:0/0/0.terrain,0/1/0.terrain,分别为左半球和右半球(具体参考:https://blog.csdn.net/u013929284/artic ...

  6. geotrellis使用(三十五)Cesium加载geotrellis TMS瓦片

    前言 做任何事情都不是想象中的那么简单.好久没有更新技术博客了,跟最近瞎忙有很大关系,虽说是瞎忙也抽空研究了些技术. 主要是前端渲染,像原生的WebGL和Cesium.WebGL写了几篇博客,自我感觉 ...

  7. Cesium入门7 - Adding Terrain - 添加地形

    Cesium入门7 - Adding Terrain - 添加地形 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com ...

  8. Cesium原理篇:3最长的一帧之地形(2:高度图)

           这一篇,接着上一篇,内容集中在高度图方式构建地球网格的细节方面.        此时,Globe对每一个切片(GlobeSurfaceTile)创建对应的TileTerrain类,用来维 ...

  9. cesium模型加载-加载fbx格式模型

    整体思路: fbx格式→dae格式→gltf格式→cesium加载gltf格式模型 具体方法: 1. fbx格式→dae格式 工具:3dsMax, 3dsMax插件:OpenCOLLADA, 下载地址 ...

随机推荐

  1. IIS6.0 IIS7.5应用程序池自动停止的解决方法

    前边提到由win2003升级到win2008 server r2 64位系统,然后用了几个小时配置IIS7.5+PHP+MYSQL等的环境,先是遇到IIS7.5下PHP访问慢的问题,解决之后又出了新的 ...

  2. discuz后台开发常用函数

    showsetting()表单显示 返回值:无 参数: $setname - 指定输出标题,如:setting_basic_bbname, 自动匹配描述文字为:setting_basic_bbname ...

  3. MSSQL中建立分区表(转载备忘)

    转载自CSDN地址:http://bbs.csdn.net/topics/330087045 SQL Server 2005 分区表实践——建立分区表(partition table) 问题:有一个订 ...

  4. Hibernate 抓取策略fetch-2 (批量抓取batch-size以及hibernate.jdbc.fetch_size、hibernate.jdbc.batch_size)

    类关系: User N~1 Group 测试代码: System.out.println("1"); List stuList = session.createQuery(&quo ...

  5. 修改页面中所有TextBox控件的样式--CSS

    1.HTML <div> TextBox<br /> <input type="text" name="name" value=& ...

  6. 【SSH系列】深入浅出SpringMvc+入门Demo

    Spring MVC框架是有一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC和Struts.Strut ...

  7. 图像处理及opencv汇总

    OPENCV——C++ 1.windows基于vs2017的opencv安装 2.为opencv添加contrib库 3.opencv源码编写规则 4.OpenCV库框架结构 5.OpenCV从2到3 ...

  8. Java各个知识点详解总结

    Java基础知识总结 写代码: 1,明确需求.我要做什么? 2,分析思路.我要怎么做?1,2,3. 3,确定步骤.每一个思路部分用到哪些语句,方法,和对象. 4,代码实现.用具体的java语言代码把思 ...

  9. com.baidu.mapapi.CoordType

    2.2.2升级到3.0.1百度报错了, 一:请检查.jar,.so是否是最新的 二:clear

  10. [转帖]IPV6取代IPV4之路 为何道阻且长?

    IPV6取代IPV4之路 为何道阻且长? 经济学人公众号 IPV6作为IPV4的续命神术,从被提出到现今,逾26年之久.而IPV6在中国更是犹抱琵琶半遮面,千呼万唤难出来,IPV6取代IPV4之路,为 ...