参考资料:

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. MobX入门示例

    在相当长的一段时间内,Redux 都是前端开发人员作为状态管理的首先框架,如果不会 Redux,你都不好意思跟别人说自己是搞前端的. 没过多久,开发者们开始意识到,这东西虽说盛行,但它并没有传说中的那 ...

  2. 破解CentOS7的root及加密grub修复实战

    破解CentOS7的root及加密grub修复实战 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.破解CentOS7的root口令方案1 1>.启动时任意键暂停启动 2& ...

  3. Httpd服务入门知识-Httpd服务安装

    Httpd服务入门知识-Httpd服务安装 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Httpd概述 1>.Httpd介绍 20世纪90年代初,国家超级计算机应用中心 ...

  4. 将python图片转为二进制文本的实例

    https://www.jb51.net/article/155342.htm 写在最前面: 我在研究机器学习的过程中,给的数据集是手写数字图片被处理后的由0,1表达的txt文件,今天写一写关于图片转 ...

  5. MySQL定义char和varchar类型,utf8编码,则最大值为多少?

    有道面试题:若一张表中只有一个字段VARCHAR(N)类型,utf8编码,则N最大值为多少?先明白计算的一些规则限制 4.0版本以下,varchar(20),指的是20字节,如果存放UTF8汉字时,只 ...

  6. 项目Beta冲刺(团队)——05.24(2/7)

    项目Beta冲刺(团队)--05.24(2/7) 格式描述 课程名称:软件工程1916|W(福州大学) 作业要求:项目Beta冲刺(团队) 团队名称:为了交项目干杯 作业目标:记录Beta敏捷冲刺第2 ...

  7. mocker-api 原理

    项目网址:https://github.com/jaywcjlove/mocker-api 作用有2个: 运行dev命令后,访问本地开启服务接口,模拟数据: 访问本机接口时,代理到其它服务器,即调用其 ...

  8. 20180414模拟赛T2——拼图

    拼图 源程序名 puzzling.??? (PAS,BAS,C,CPP) 可执行文件名 puzzling.EXE 输入文件名 puzzling.IN 输出文件名 puzzling.OUT 时间限制 1 ...

  9. Ruby break, next, redo, retry

    # -*- coding: UTF-8 -*- # E3.10-5.rb 演示break, next, redo, retry puts "演示break" c='a' for i ...

  10. Ofbiz项目学习——阶段性小结——更新数据

    一.根据一个字段进行修改 /** * 根据一个字段进行修改(这个条件字段可以是主键, 也可以不是主键) * @param dctx * @param context * @return */ publ ...