注: 近期几篇关于 三维渲染优化 的文章只专注于 Primitive。关于 Model, Cesium3DTileset 的内容另作安排。
上一篇介绍了 GPU三维渲染优化 - 批量渲染
本篇来说说 Primitive 的 批量渲染 (Batch Rendering)。
核心内容一部分在 BatchTable, 另一部分在 PrimitivePipeline.combineGeometry
由于BatchTable涉及到纹理相关内容, 准备另起一篇文章介绍。
PrimitivePipeline.combineGeometry(本函数属于同步加载部分, 异步加载的架构思想类似)里:
本篇先说PrimitivePipeline.combineGeometry部分(下面的可以粘贴复制定位到源代码里)
packages/engine/Source/Scene/PrimitivePipeline.js:326
直接定位到的是下面一行代码
geometries = geometryPipeline(parameters);
这个函数做了三件事:
-
将所有实例的几何数据(position, normal, st等)全都转换为世界坐标。
-
为每个实例的几何添加 Batch Id。
-
将所有实例的geometry 合并到 一个 geometry。
-
为GPU分割 像 position 的 双精度(Double)的属性数据, 在着色器可以看到 类似 position3DHigh, position3DLow这样的字眼。
-
使用 Oct Encoding(八面体编码)压缩 法线 及 纹理坐标 来减轻内存带宽的压力(下一篇详细说说 八面体编码 解码)。
其中 合并几何的核心是下面一段代码, 也是Primitive能够批量渲染的核心逻辑
// Combine attributes from each geometry into a single typed array
for (name in attributes) {
if (attributes.hasOwnProperty(name)) {
values = attributes[name].values;
k = 0;
for (i = 0; i < length; ++i) {
sourceValues = instances[i][propertyName].attributes[name].values;
sourceValuesLength = sourceValues.length;
for (j = 0; j < sourceValuesLength; ++j) {
values[k++] = sourceValues[j];
}
}
}
}
// Combine index lists
let indices;
if (haveIndices) {
let numberOfIndices = 0;
for (i = 0; i < length; ++i) {
numberOfIndices += instances[i][propertyName].indices.length;
}
const numberOfVertices = Geometry.computeNumberOfVertices(
new Geometry({
attributes: attributes,
primitiveType: PrimitiveType.POINTS,
}),
);
const destIndices = IndexDatatype.createTypedArray(
numberOfVertices,
numberOfIndices,
);
let destOffset = 0;
let offset = 0;
for (i = 0; i < length; ++i) {
const sourceIndices = instances[i][propertyName].indices;
const sourceIndicesLen = sourceIndices.length;
for (k = 0; k < sourceIndicesLen; ++k) {
destIndices[destOffset++] = offset + sourceIndices[k];
}
offset += Geometry.computeNumberOfVertices(instances[i][propertyName]);
}
indices = destIndices;
}
位置(下面的可以粘贴复制定位到源代码里)
packages/engine/Source/Core/GeometryPipeline.js:977
Primitive目前并没有使用 实例化渲染来做大规模数据的处理, 而是通过合并几何数据Buffer(position, normal, st等)来达到批量渲染的目的。
以下是几何数据合并与实例化渲染的优劣对比:
| 对比维度 | 几何数据合并 | 实例化渲染 |
|---|---|---|
| 主要特点 | 将多个实例的几何数据合并到统一缓冲区,作为单个对象提交给 GPU 渲染。 | 每个实例共享同一个几何体,通过实例属性(如矩阵、颜色)区分。 |
| 兼容性 | 无需依赖 WebGL 2 或扩展,兼容所有支持 WebGL 的设备。 | 依赖 WebGL 2 或 ANGLE_instanced_arrays 扩展,设备支持有限。 |
| GPU 数据传输 | 合并后的数据以线性缓冲区形式传输,减少小批次传输开销。 | 每帧只需传输实例属性,几何数据无需重复传输。 |
| 内存占用 | 合并后的顶点缓冲区可能占用较多 GPU 内存。 | 仅存储一个几何体,但需要额外存储实例属性缓冲区。 |
| 运行时开销 | 减少 GPU 上的矩阵计算,渲染时直接使用世界坐标。 | 运行时需要通过矩阵计算每个实例的顶点位置,GPU 开销更大。 |
| 动态场景支持 | 更新频繁时需重新生成缓冲区,效率较低。 | 动态更新实例属性(如变换矩阵)更高效,适合动态场景。 |
| 剔除效率 | 粗粒度剔除,无法剔除单个实例。 | 基于实例的包围体进行细粒度剔除,更精确。 |
| LOD 管理 | 合并几何时可直接生成多级细节模型,支持静态 LOD 优化。 | 通过实例的距离调整属性,动态支持 LOD。 |
| 适用场景 | 静态场景、大量不同几何体组合的场景。 | 高度重复的几何体,如森林、城市中的重复模型。 |
| 实现复杂性 | 实现简单,只需预处理和合并几何数据即可。 | 实现复杂,需要管理实例属性缓冲区和着色器逻辑。 |
| 性能 | 在支持有限动态变化的场景中表现良好,尤其是静态实例场景。 | 在高度动态、重复性高的场景中表现更优。 |
总结
-
几何数据合并: 优点在于兼容性强,适合静态或变化较少的场景,缺点是动态性支持较弱。
-
实例化渲染: 优点在于动态性支持好,适合大量重复几何体,缺点是实现复杂性较高,且对硬件支持有要求。
理论上看. 合并几何数据不支持 动态场景的 渲染, 但是Cesium在渲染管道上加了 BatchTable 这个东西。通过这个玩意儿我们可以动态修改场景中的实例。
当然上面表格中列出的 合并几何数据的一些劣势, Cesium也采取相关优化方案避免掉了, 后面会一一介绍。
下一篇Cesium的BatchTable
Cesium Primitive批量渲染优化解析
232

被折叠的 条评论
为什么被折叠?



