Cesium 渲染优化之批量渲染 - Primitive

Cesium Primitive批量渲染优化解析

原文链接

注: 近期几篇关于 三维渲染优化 的文章只专注于 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);

这个函数做了三件事: 

  1. 将所有实例的几何数据(position, normal, st等)全都转换为世界坐标。

  2. 为每个实例的几何添加 Batch Id。

  3. 优化顶点缓存

  4. 将所有实例的geometry 合并到 一个 geometry。

  5. 为GPU分割 像 position 的 双精度(Double)的属性数据, 在着色器可以看到 类似  position3DHigh, position3DLow这样的字眼。

  6. 使用 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批量渲染机制 在Cesium 1.74中,高性能渲染大量线条的核心在于Primitive API的批量处理、着色器优化和数据合并策略[^1]。Primitive API允许开发者将多个几何实例合并为一个Primitive对象进行渲染,这样可以减少GPU的调用次数,提高渲染性能。例如,在批量绘制线条时,可以将多个线条的几何信息合并到一个`GeometryInstance`数组中,然后创建一个`Primitive`对象来渲染这些线条。以下是一个简单的示例代码: ```javascript // 创建多个几何实例 var instances = []; for (var i = 0; i < 100; i++) { var positions = [ Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883), Cesium.Cartesian3.fromDegrees(-75.60777, 40.04883) ]; var geometry = new Cesium.PolylineGeometry({ positions: positions, width: 5 }); var instance = new Cesium.GeometryInstance({ geometry: geometry }); instances.push(instance); } // 创建Primitive对象 var primitive = new Cesium.Primitive({ geometryInstances: instances, appearance: new Cesium.PerInstanceColorAppearance({ flat: true }) }); // 将Primitive添加到场景中 viewer.scene.primitives.add(primitive); ``` ### 修改实体集合触发重新渲染的原理和情况 #### 原理 Cesium渲染流程是基于场景图的,当实体集合发生变化时,Cesium会检测到这些变化,并重新计算场景图的状态。如果实体的属性(如位置、颜色、大小等)发生了改变,或者有新的实体添加到集合中,或者有实体从集合中移除,Cesium会标记这些变化,并在下一帧渲染时更新场景。 #### 情况 - **添加实体**:当使用`viewer.entities.add`方法添加新的实体时,Cesium会将新实体添加到实体集合中,并触发重新渲染。例如: ```javascript var entity = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883), point: { pixelSize: 10, color: Cesium.Color.RED } }); ``` - **移除实体**:当使用`viewer.entities.remove`方法移除实体时,Cesium会将该实体从实体集合中移除,并触发重新渲染。例如: ```javascript viewer.entities.remove(entity); ``` - **修改实体属性**:当修改实体的属性时,Cesium会检测到这些变化,并触发重新渲染。例如: ```javascript entity.point.color = Cesium.Color.BLUE; ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值