快速导航
Cesium源码解析一(搭建开发环境)
Cesium源码解析二(terrain文件的加载、解析与渲染全过程梳理)
Cesium源码解析三(metadataAvailability的含义)
Cesium源码解析四(metadata元数据拓展中行列号的分块规则解析)
Cesium源码解析五(Quantized-Mesh(.terrain)格式文件在CesiumJS和UE中加载情况的对比)
1.前言
上一篇中我们梳理了 Cesium 加载、解析、渲染 terrain 的整个流程,这一篇我们就深入细节,看一下 layer.json 中的 metadataAvailability 参数的含义。一般使用 Cesium 的同学可能也注意不到这个参数,之所以要单独拿出来说这个,是因为我们遇到了一个问题,使用 GeoServer 发布的 terrain 服务,在 UE 中加载没问题,在 Cesium 中加载却一直报错: TypeError: Cannot read properties of undefined (reading ‘addAvailableTileRange’) ,由此引发了我们不断深入,终于发现了这个参数的真正含义,本篇我们就来讲一讲。
2.json文件的结构
layer.json这个文件,相信使用过 Cesium 的同学应该都不陌生了,我们来看下它长什么样子。
如图,这是一个 CesiumLab 处理数据以后,给出的 layer.json ,我们可以看到里面有个参数 metadataAvailability ,它的值是10。这是什么意思?我这里可以直接给出结论,后面我们再详细看:
metadataAvailability 翻译过来叫做:元数据可用性,它的真正作用是,计算当前层级的父层级中,哪一级的 terrain 数据中包括了元数据的信息。
3.元数据的解析过程
元数据的解析过程是在 CesiumTerrainProvider.createQuantizedMeshTerrainData 函数中完成的,详细的解析解析过程我们在第一篇中进行了介绍,不清楚的同学可以移步 Cesium源码解析一(terrain文件的加载、解析与渲染全过程)。这里我们只把里面核心的一段放出来,这是一个 while 循环:
while (pos < view.byteLength) {
var extensionId = view.getUint8(pos, true);
pos += Uint8Array.BYTES_PER_ELEMENT;
var extensionLength = view.getUint32(pos, littleEndianExtensionSize);
pos += Uint32Array.BYTES_PER_ELEMENT;
console.log( level, x, y,"extensionid="+extensionId+",extensionLength="+extensionLength+",byteLength="+view.byteLength);
if (
extensionId === QuantizedMeshExtensionIds.OCT_VERTEX_NORMALS &&
provider._requestVertexNormals
) {
//数据中有光照并且也请求了光照
encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2);
} else if (
extensionId === QuantizedMeshExtensionIds.WATER_MASK &&
provider._requestWaterMask
) {
//数据中有水面并且也请求了水面
waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength);
} else if (
extensionId === QuantizedMeshExtensionIds.METADATA &&
provider._requestMetadata
) {
//数据中有元数据并且也请求了元数据
var stringLength = view.getUint32(pos, true);
console.log("metadata_length="+stringLength);
if (stringLength > 0) {
var metadata = getJsonFromTypedArray(
new Uint8Array(buffer),
pos + Uint32Array.BYTES_PER_ELEMENT,
stringLength
);
var availableTiles = metadata.available;
if (defined(availableTiles)) {
for (var offset = 0; offset < availableTiles.length; ++offset) {
var availableLevel = level + offset + 1;
var rangesAtLevel = availableTiles[offset];
var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(
availableLevel
);
for (
var rangeIndex = 0;
rangeIndex < rangesAtLevel.length;
++rangeIndex
) {
var range = rangesAtLevel[rangeIndex];
var yStart = yTiles - range.endY - 1;
var yEnd = yTiles - range.startY - 1;
provider.availability.addAvailableTileRange(
availableLevel,
range.startX,
yStart,
range.endX,
yEnd
);
layer.availability.addAvailableTileRange(
availableLevel,
range.startX,
yStart,
range.endX,
yEnd
);
}
}
}
}
layer.availabilityTilesLoaded.addAvailableTileRange(level, x, y, x, y);
}
pos += extensionLength;
}
在第0级和10级的时候会进行元数据的解析来构建四叉树。我们以加载 Cesium ion 提供的 terrain 数据为例来看下解析到的元数据数据,当 zxy=0,0,0是这样的:
当 zxy=0,1,0是这样的:
有没有发现解析出来的数组的长度都是10。但这只是表象,千万不要以为这个10就是 metadataAvailability 的值。因为 metadataAvailability 是用来计算的,我们接着往下跟。
4.计算父层级中哪一级包含了元数据信息以构建四叉树
这个功能是在 CesiumTerrainProvider.getAvailabilityTile 函数中实现的。我们来看下代码:
function getAvailabilityTile(layer, x, y, level) {
if (level === 0) {
return;
}
//layer.availabilityLevels就是layer.json中的metadataAvailability的值
var availabilityLevels = layer.availabilityLevels;
var parentLevel =
level % availabilityLevels === 0
? level - availabilityLevels
: ((level / availabilityLevels) | 0) * availabilityLevels;
var divisor = 1 << (level - parentLevel);
var parentX = (x / divisor) | 0;
var parentY = (y / divisor) | 0;
return {
level: parentLevel,
x: parentX,
y: parentY,
};
}
观察上述代码,我们发现,调用过程中,是用当前层级 level 对 layer.availabilityLevels,也就是 metadataAvailability 进行取余,来计算出一个父层级。
如果当前 level 的值是0-9,那么计算出来就是0,如果当前 level 是10-20,计算出来的值就是10,也就是说,只有第0级和第10的 terrain 文件中才存储了元数据的信息。当我们跟踪解析元数据的代码时,也能发现这一规律。
然后我们再来看下此时的调用栈:
发现原来是递归调用,注意,Scene.updateAndExecuteCommands ,这是一个重要过程,我们在上一篇中分析过,这个过程是执行渲染命令,它使用的数据是上一帧准备好的数据,因为现在的浏览器一般都是每秒60帧,所以,人的是感觉就是实时渲染的。
3.总结
本篇博文中,我们对 metadataAvailability 进行了深入解析,并结合 Cesium 的整个渲染过程进行了分析,使我们的认识更加深刻了。那么本次就讲到这里了,回见~