Cesium的3D Tiles是一种用于流式传输和渲染大规模3D地理空间数据的开放规范。本文详细解析Cesium引擎如何加载、解析和渲染3D Tiles数据。
jspackages/engine/Source/Scene/Cesium3DTileset.js - 瓦片集管理
packages/engine/Source/Scene/Cesium3DTile.js - 单个瓦片
packages/engine/Source/Scene/Cesium3DTileContent.js - 瓦片内容基类
packages/engine/Source/Scene/Batched3DModel3DTileContent.js - B3DM瓦片内容
packages/engine/Source/Scene/Instanced3DModel3DTileContent.js - I3DM瓦片内容
packages/engine/Source/Scene/PointCloud3DTileContent.js - PNTS瓦片内容
packages/engine/Source/Scene/Composite3DTileContent.js - 复合瓦片内容
packages/engine/Source/Scene/Model.js - 模型处理
packages/engine/Source/Scene/ModelDrawCommand.js - 模型绘制命令
Cesium3DTileset是整个瓦片集的容器,负责管理瓦片树结构和调度加载请求。
Cesium3DTileset.js
jsCesium3DTileset.prototype.update = function(frameState) {
// ...
this._requestedTiles.length = 0;
// 获取可见瓦片
this._selectedTiles.length = 0;
this._selectedTilesToStyle.length = 0;
if (defined(this._loadTimestamp)) {
processTiles(this, frameState);
}
// ...
};
function processTiles(tileset, frameState) {
// ...
// 视锥体裁剪和瓦片选择
traverseAndSelect(tileset, frameState);
// 更新瓦片几何误差、优先级
updateTiles(tileset, frameState);
// 发送网络请求,加载未加载的瓦片
requestTiles(tileset, frameState);
// ...
}
Cesium3DTileset.js
jsfunction requestTiles(tileset, frameState) {
// 对请求队列排序
tileset._requestedTiles.sort(sortRequestByPriority);
for (let i = 0; i < tileset._requestedTiles.length; ++i) {
const tile = tileset._requestedTiles[i];
// 触发瓦片内容加载
tileset._tileResource.retryAttempts = tileset._retryAttempts;
tile.requestContent(tile, tileset, tileset._tileResource);
}
}
Cesium3DTile.prototype.requestContent = function() {
// ...
// 创建请求,加载瓦片二进制数据
const request = new Request({
throttle: false,
throttleByServer: true,
type: RequestType.TILES3D,
priorityFunction: createPriorityFunction(this),
serverKey: serverKey
});
// 发起请求
const promise = requestResource.fetchArrayBuffer(request);
// 处理请求结果
const contentPromise = promise.then((arrayBuffer) => {
// 创建瓦片内容对象
const content = createContent(this, arrayBuffer, contentFactory);
this._content = content;
this.contentState = Cesium3DTileContentState.PROCESSING;
// 解析瓦片内容
return content.process(this._tileset.mainThreadOrWorkerWorkerScheduler);
});
// ...
};
Batched 3D Model (B3DM) 是最常用的3D Tiles格式,包含多个批处理的3D模型。
Batched3DModel3DTileContent.js
jsfunction Batched3DModel3DTileContent(
tileset,
tile,
resource,
arrayBuffer,
byteOffset
) {
// ...
// 解析二进制头部
const headerView = new DataView(arrayBuffer);
const byteLength = headerView.getUint32(byteOffset, true);
byteOffset += 4;
// 解析glTF标志和版本
const gltfFormat = headerView.getUint32(byteOffset, true);
// ...
// 提取特性表
const featureTableJsonByteLength = headerView.getUint32(byteOffset, true);
byteOffset += 4;
// ...
// 解析特性表数据
const featureTableBinaryByteLength = headerView.getUint32(byteOffset, true);
byteOffset += 4;
const batchTableJsonByteLength = headerView.getUint32(byteOffset, true);
byteOffset += 4;
// ...
// 提取glTF数据
const gltfByteLength = byteLength - byteOffset;
let gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength);
// ...
// 创建glTF模型
const model = new Model({
gltf: gltfView,
// ...其他参数
});
// 存储解析结果
this._model = model;
this._batchTable = batchTable;
// ...
}
Model类负责解析glTF数据,提取顶点属性并创建WebGL资源。
Model.js
jsModel.prototype.initialize = function(arrayBuffer) {
// ...
// 解析glTF JSON
let gltf;
if (defined(arrayBuffer)) {
gltf = parseGLTF(arrayBuffer);
}
// ...
// 处理顶点缓冲区
ForEach.mesh(gltf, (mesh) => {
ForEach.meshPrimitive(mesh, (primitive) => {
// 获取顶点索引
const indices = accessorId(primitive.indices);
// 获取顶点位置
const positionId = primitive.attributes.POSITION;
// 获取法线数据
const normalId = primitive.attributes.NORMAL;
// 获取纹理坐标
const texcoordId = primitive.attributes.TEXCOORD_0;
// ...
});
});
// ...
};
Model.js
jsfunction createBuffers(model, frameState) {
const loadResources = model._loadResources;
const bufferLoads = loadResources.bufferLoads;
for (const bufferLoad of bufferLoads) {
// 创建WebGL缓冲区
const buffer = Buffer.createVertexBuffer({
context: frameState.context,
typedArray: bufferLoad.typedArray,
usage: BufferUsage.STATIC_DRAW
});
// 保存缓冲区引用
bufferLoad.vertexBuffer = buffer;
}
}
顶点数组对象(VAO)创建
jsfunction createVertexArrays(model, frameState) {
// ...
for (const primitive of primitivesByteLength) {
// 获取顶点属性描述
const attributes = primitive.attributes;
const vaAttributes = [];
// 处理每个顶点属性(位置、法线、纹理坐标等)
for (const attribute of attributes) {
const accessor = accessors[attribute.accessor];
const bufferLoad = bufferLoads[accessor.bufferView];
// 创建顶点属性描述
vaAttributes.push({
index: attribute.index,
vertexBuffer: bufferLoad.vertexBuffer,
componentsPerAttribute: accessor.componentsPerAttribute,
componentDatatype: accessor.componentDatatype,
normalize: attribute.normalize,
offsetInBytes: accessor.byteOffset + attribute.byteOffset,
strideInBytes: accessor.byteStride
});
}
// 创建WebGL顶点数组对象
const va = new VertexArray({
context: frameState.context,
attributes: vaAttributes,
indexBuffer: defined(primitive.indices) ?
indexBufferLoads[primitive.indices].indexBuffer : undefined
});
// 保存VAO引用
primitive.va = va;
}
}
创建绘制命令
ModelDrawCommand.js
jsModelDrawCommand.prototype.buildCommand = function(
model,
context,
frameState,
modelMatrix,
runtimeNode,
lightColor
) {
// ...
// 创建绘制命令
const command = new DrawCommand({
boundingVolume: new BoundingSphere(), // 模型的包围盒
modelMatrix: modelMatrix, // 模型变换矩阵
primitiveType: primitive.primitiveType, // 图元类型(如三角形)
vertexArray: primitive.va, // 顶点数组对象
count: primitive.count, // 索引或顶点数量
shaderProgram: shaderProgram, // 着色器程序
uniformMap: uniformMap, // uniform变量映射
renderState: renderState, // 渲染状态
pass: pass // 渲染通道
});
return command;
};
绘制命令执行
DrawCommand.js
jsDrawCommand.prototype.execute = function(context, passState) {
// ...
// 绑定着色器程序
shaderProgram._bind();
// 设置Uniform变量
let uniform;
let uniformId;
for (uniformId in uniforms) {
if (uniforms.hasOwnProperty(uniformId)) {
uniform = uniforms[uniformId];
uniform._set();
}
}
// 绑定顶点数组
vertexArray._bind();
// 应用渲染状态
context._applyRenderState(renderState);
// 绘制图元
if (defined(this.count)) {
if (defined(instanceCount)) {
// 实例化绘制
context._gl.drawElementsInstanced(
this.primitiveType,
this.count,
indexDatatype,
offset,
instanceCount
);
} else {
// 普通索引绘制
context._gl.drawElements(
this.primitiveType,
this.count,
indexDatatype,
offset
);
}
} else {
// 数组绘制
context._gl.drawArrays(this.primitiveType, offset, vertexCount);
}
// 解除绑定
vertexArray._unBind();
shaderProgram._unBind();
};
Buffer.js
jsfunction Buffer(options) {
// ...
this._buffer = context._gl.createBuffer();
// 绑定缓冲区
const target = this._target;
const gl = context._gl;
gl.bindBuffer(target, this._buffer);
// 上传数据到GPU
gl.bufferData(target, typedArray, usage);
// 跟踪显存使用
this._sizeInBytes = sizeInBytes;
// 解除绑定
gl.bindBuffer(target, null);
}
VertexArray.js
jsfunction VertexArray(options) {
// ...
const va = gl.createVertexArray();
gl.bindVertexArray(va);
// 绑定顶点属性
for (const attribute of attributes) {
// 绑定顶点缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, attribute.vertexBuffer._buffer);
// 启用顶点属性
gl.enableVertexAttribArray(attribute.index);
// 定义顶点属性格式
gl.vertexAttribPointer(
attribute.index, // 属性位置
attribute.componentsPerAttribute, // 每个顶点的组件数量(如位置=3,法线=3)
attribute.componentDatatype, // 数据类型(如FLOAT)
attribute.normalize, // 是否归一化
attribute.strideInBytes, // 步长
attribute.offsetInBytes // 偏移量
);
// 设置顶点属性除数(用于实例化渲染)
if (defined(attribute.instanceDivisor)) {
gl.vertexAttribDivisor(attribute.index, attribute.instanceDivisor);
}
}
// 绑定索引缓冲区(如果有)
if (defined(indexBuffer)) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer._buffer);
}
// 解除VAO绑定
gl.bindVertexArray(null);
this._va = va;
}
ShaderProgram.js
jsfunction ShaderProgram(options) {
// ...
// 创建顶点着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
// 创建片元着色器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
// 创建着色器程序
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 绑定属性位置
for (const attributeLocation in attributeLocations) {
gl.bindAttribLocation(
program,
attributeLocations[attributeLocation],
attributeLocation
);
}
// 链接程序
gl.linkProgram(program);
// 检查编译和链接状态
// ...
// 获取Uniform位置
const uniformLocations = {};
const numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < numberOfUniforms; ++i) {
const activeUniform = gl.getActiveUniform(program, i);
uniformLocations[activeUniform.name] = gl.getUniformLocation(
program,
activeUniform.name
);
}
this._program = program;
this._uniformLocations = uniformLocations;
}
法线压缩
jsAttributeCompression.octEncodeInRange = function(normal, rangeMax) {
// 法线压缩到8位或16位
const x = normal.x;
const y = normal.y;
const z = normal.z;
const octX = (x >= 0.0 ? 1.0 : -1.0) * (1.0 - Math.abs(y / (Math.abs(x) + Math.abs(y) + Math.abs(z))));
const octY = (y >= 0.0 ? 1.0 : -1.0) * (1.0 - Math.abs(z / (Math.abs(x) + Math.abs(y) + Math.abs(z))));
// 从[-1,1]映射到[0,rangeMax]
return new Cartesian2(
AttributeCompression.scaleToSnorm(octX, rangeMax),
AttributeCompression.scaleToSnorm(octY, rangeMax)
);
};
jsfunction createFeatures(content) {
// ...
// 分批处理,减少绘制调用
// batchTable中包含多个实体的元数据
const featuresLength = content._featuresLength;
for (let i = 0; i < featuresLength; ++i) {
// 创建每个批次的特性对象
content._features.push(new Cesium3DTileFeature(content, i));
}
}
jsCesium3DTilesetCache.prototype.add = function(tile) {
// ...
// LRU缓存策略
if (this._list.length > this._maximumMemoryUsageInBytes) {
// 淘汰最久未使用的瓦片
this.unloadTile(this._last);
}
// 将瓦片添加到缓存头部
tile._cacheNode = this._list.add(tile);
};
jsfunction createFeatures(content) {
// ...
// 实例化渲染优化,一次绘制调用渲染多个实例
const instancesLength = content._instancesLength;
// 创建实例化属性缓冲区
const instancedAttributes = [];
instancedAttributes.push({
index: attributeLocation,
vertexBuffer: createInstancedBuffer(/* 实例位置数据 */),
componentsPerAttribute: 3,
instanceDivisor: 1 // 每个实例一条数据
});
// 添加到顶点数组对象
for (const attribute of instancedAttributes) {
vertexArray.addAttribute(attribute);
}
}
jsfunction updateVisibility(tileset, frameState) {
const cullingVolume = frameState.cullingVolume;
const cameraPosition = frameState.camera.positionWC;
// 遍历瓦片树
const stack = tileset._stack;
stack.push(tileset._root);
while (stack.length > 0) {
const tile = stack.pop();
// 计算与视锥体的关系
const boundingVolume = tile.boundingVolume;
const visibility = boundingVolume.computeVisibility(cullingVolume);
// 完全不可见的瓦片裁剪掉
if (visibility === CullingVolume.MASK_OUTSIDE) {
continue;
}
// 距离计算
const distanceToCamera = tile._distanceToCamera =
tile.distanceToTile(frameState);
// 处理可见瓦片
// ...
}
}
本文作者:幽灵
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 幽灵AI 许可协议。转载请注明出处!