Cesium原理篇:Batch

本文深入探讨了Cesium中的BatchTable机制,解释了如何通过GeometryInstance实现几何体的批量处理,以及如何利用BatchTable优化渲染流程。文章还介绍了不同材质在处理上的差异,以及如何根据材质特性进行高效的分组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       通过之前的Material和Entity介绍,不知道你有没有发现,当我们需要添加一个rectangle时,有两种方式可供选择,我们可以直接添加到Scene的PrimitiveCollection,也可以构造一个Entity,添加到Viewer的EntityCollection中,代码如下:


       两者有何不同,为什么还要提供Entity这种方式,绕了一大圈,最后照样是一个primitive。当然,有一个因素是后者调用简单,对用户友好。但还有一个重点是内部在把Entity转为Primitive的这条生产线上,会根据Entity的不同进行打组分类,好比快递的分拣线会将同一个目的地的包裹分到一类,然后用一个大的箱子打包送到该目的地,目的就是优化效率,充分利用显卡的并发能力。

       在Entity转为Primitive的过程中,GeometryInstance是其中的过渡类,以Rectangle为例,我们看看构造GeometryInstance的过程:

       首先,该材质_materialProperty就是构建Entity时传入的材质对象,attributes则用来标识该Geometry实例化的attribute属性,Cesium内部判断该材质是否为color类型,进而对应不同的实例化attribute。在其他GeometryUpdater方法中都是此逻辑,因此在材质的处理上,只对color进行了实例化的处理。这里留意一下appearance参数,可以看到主要对应perInstanceColorAppearanceType和materialAppearanceType两种,分别封装ColorMaterialProperty和其他MaterialProperty,这个在之前的Material中已经讲过。

Batch

       Cesium通过GeometryInstance作为标准,来达到物以类聚鸟以群分的结果,这个标准就是如上的介绍,有了标准还不够,还需要为这个标准搭建一条流水线,将标准转化为行动。这就是Batch的作用。之前在Entity中提到GeometryVisualizer提供了四种Batch,我们以StaticGeometryColorBatch和StaticGeometryPerMaterialBatch为例,对比说明一下这个流水线的流程。

       我们在此来到DataSourceDisplay类,初始化是针对每一个Updater,都封装了一个Visualizer.我们还是以RectangleGeometry为例展开:

       而每一次添加的Entity都会以事件的方式通知到每一个Updater绑定的GeometryVisualizer,调用insertUpdaterIntoBatch方法,根据每个Entity材质的不同放到对应的Batch队列中,其实Cesium的batch很简单,就是看是否是color类型的材质,只有这一个逻辑判断:

       我们先看ColorMaterialProperty材质的处理方式:

       可见,按照颜色是否透明分为两类,这个不难理解,因为这两类对应的PASS不同,渲染的优先级不一样。同时,在添加到对应batch队列前,会调用Updater.createFillGeometryInstance方法创建该Geometry对应的Instance。因此,这里体现了Cesium的一个规范,每一个GeometryGraphics类型都对应了一个该Geometry的Updater类,该Updater类通过一套create*geometryInstance方法,实现不同的GeometryGraphics到GeometryInstance的标准化封装。下面是RectangleGeometryUpdater提供的三个create方法:

       前两个不用多说,看注释。后一个一看dynamic,看上去牛逼,其实只是将creategeometryinstance的过程延后进行,不是在add中创建,而是在update的时候创建。接着在Batchupdate中,将batch队列中所有的geometryinstances封装成一个Primitve,这个流水线至此结束。因为我们在之前的Entity中介绍过这个过程,所以不在此展开。下面,我们在看看StaticGeometryPerMaterialBatch,对比一下两者的不一样。

      可以看到,非颜色材质的batch里面还有一个items数组,会判断当前的updater中的材质是否存在于items数组中,如果没有,则根据该材质创建一个新的batch,并添加到items数组中。因为多了这一层,所以之前Updater.createFillGeometryInstance的调用延后到batch.add的过程中,并无其他不同。然后调用batch.update,以材质类型为标准创建对应的primitive。

BatchTable

       对于这些实例化的属性,Primitive在update中对其进行处理,思路就是将这些值保存到一张RGBA的纹理,并根据实例化属性的长度构建对应的VBO,从而方便Shader中的使用。下面,我们以两个Rectangle为例,来看看详细的过程。

createBatchTable

       如上,可以看到BatchTable可以认为是这些instance的一个实例化属性表,属性也是按照VBO的结构来设计的,就是为了后续shader中使用的方便。

BatchTable.prototype.update

       创建完BatchTable,则调用update,将该Table的属性值以RGBA的方式保存到一张Texture中,这类似于一个float纹理,但通用性更强一些,毕竟有一些浏览器竟然不支持float纹理。比如ColorMaterialProperty,里面有color,show,distanceDisplayCondition三个实例化属性,分别控制颜色,是否可见以及可见范围的控制。实际上,还包括pickColor,boundingSphereRadius,boundingSphereCenter3DHigh,boundingSphereCenter3DLow,boundingSphereCenter2DHigh,boundingSphereCenter2DLow共计9个属性,其中Center占了四个属性,我们后续有机会在详细说一下Cesium的算法细节,目的是为了避免近距离观察物体时因为精度导致的抖动问题,用两个float来表示一个double的思路来解决。这样,假如我们有两个instance对象,则该纹理x维度是9*2 = 18,而大多数情况下,y维度则始终为1(只要x维度的长度不超过显卡最大纹理长度的限制),相当于一个一维纹理,其实就是一个hashtable。

       createTexture后就是updateTexture就是把我们之前set的attribute属性值保存到该纹理中。

updateBatchTableBoundingSpheres

       这个不多讲了,就是刚才说的,把centre的xyz每一个属性保存为两个float的过程。

createShaderProgram

  • BatchTable.prototype.getVertexShaderCallback
  • Primitive._appendShowToShader
  • Primitive._appendDistanceDisplayConditionToShader
  • Primitive._updateColorAttribute

       通过如上几个函数,添加处理BatchTable部分的片源着色器代码。

createCommands

       如上,在创建command时,创建对应的uniformMap,传入uniform变量。

总结

       如上就是Cesium批次流程的大概流程,着重介绍了BatchTable这种思想,如果我们在实际设计中这也是值得我们学习借鉴的地方。打个比方,如果你对相机不熟悉,就用自动对焦,则基本就是傻瓜操作,而如果你比较熟悉,则可以用单反,自己来设置参数。Batch流程也是如此,只是解决了最常见,最基本的分类,如果你足够熟悉,可以基于Primitive自己来手动完成分组的过程,多快好省,可以不同类型的geometry来做一个批次,always try, never die。

       好了,这里忽略了两个地方,一个是这个面如何做到贴地,二是Geometry在渲染时抖动有是怎么一回事。前者用到了模版缓冲的方法,后者则是float精度问题导致的,我们下一节在说一下第一个问题。

<think>好的,用户想知道如何在Cesium中实现标记聚合功能。首先,我需要回忆一下Cesium的相关知识。Cesium是一个用于创建3D地球和地图应用的JavaScript库,常用于地理空间数据可视化。标记聚合(Marker Clustering)是在地图上大量标记点的情况下,将相近的标记合并成一个聚合点,以提高性能和用户体验的功能。 用户提到的其他SDK,比如Google Maps API、Mapbox等,可能都有内置的聚合功能,但Cesium本身可能没有直接提供这个功能。因此,可能需要通过第三方库或自定义方法实现。 首先,我需要确认Cesium是否原生支持聚合功能。根据我的知识,Cesium的Entity API允许添加大量点实体,但处理大量实体时性能可能会下降。这时候,使用聚合技术就很重要了。不过,Cesium本身没有内置的聚合功能,所以可能需要使用第三方库,比如Cesium的插件或扩展。 然后,我想到Cesium社区可能有相关的库。例如,cesium-clustering-engine或者cesium-point-cluster。需要检查这些库的可用性和兼容性。另外,用户可能需要手动实现聚合逻辑,比如基于屏幕空间的距离计算,将相近的实体分组,并用聚合点代替。 接下来,我应该考虑实现步骤。首先,收集所有需要显示的标记点,然后根据它们的屏幕坐标计算是否在同一个聚合范围内。如果多个点距离过近,就替换成一个聚合点,并显示数量。当用户放大到一定程度时,聚合点会分解成单个标记。 另外,性能优化是关键,特别是处理大量数据时。可能需要使用四叉树或其他空间索引结构来高效地分组点。同时,聚合的触发时机,比如相机移动结束后进行重新计算,避免频繁操作导致卡顿。 还需要考虑如何动态更新聚合状态。当用户缩放或移动地图时,聚合点需要根据当前视图重新计算。这可能需要监听Cesium的相机变化事件,并在变化结束后触发聚合逻辑。 最后,可能需要使用Primitive API而不是Entity API来提高性能,因为Primitive更底层,更适合处理大量动态更新的图形。 总结,实现步骤可能包括:加载数据,创建聚合算法,动态更新聚合点,优化性能。同时,引用相关的第三方库或示例代码,比如cesium-point-cluster,来帮助用户快速实现功能。</think>在Cesium中实现标记聚合功能需要结合空间索引算法与可视化控制,以下是分步实现方案: ### 一、基础实现原理 1. **空间坐标转换**:将地理坐标转换为屏幕坐标系,使用$SceneTransforms.wgs84ToWindowCoordinates$方法 2. **距离计算**:在屏幕空间计算标记间距,判断是否达到聚合阈值 3. **层级管理**:根据地图缩放级别动态调整聚合半径,公式: $$ R = \frac{baseRadius}{2^{zoom-level}} $$ 其中$baseRadius$是预设的基础聚合半径[^1] ### 二、具体实现步骤 ```javascript // 1. 创建聚合容器 const clusterer = new Cesium.EntityCluster({ enabled: true, pixelRange: 60, // 聚合像素范围 minimumClusterSize: 2 // 最小聚合数量 }); // 2. 添加数据源时启用聚合 const dataSource = new Cesium.CustomDataSource('clusterData'); dataSource.clustering = clusterer; viewer.dataSources.add(dataSource); // 3. 创建聚合显示策略 clusterer.clusterEvent.addEventListener((clusters) => { clusters.forEach(cluster => { if (cluster.entities.length > 1) { // 创建聚合图标 cluster.label.text = cluster.entities.length.toString(); cluster.billboard.image = generateClusterIcon(cluster.entities.length); } }); }); // 4. 动态更新策略 viewer.camera.moveEnd.addEventListener(() => { const zoom = viewer.camera.positionCartographic.height; clusterer.pixelRange = calculateDynamicRange(zoom); }); ``` ### 三、性能优化方案 1. **空间索引构建**:使用R-tree数据结构存储坐标点 ```javascript const tree = new RBush().load(points); ``` 2. **分批渲染**:将超过1000个的标记分块处理 $$ batchSize = \lceil \frac{totalPoints}{1000} \rceil $$ 3. **LOD控制**:根据视距设置不同层级的聚合策略 ```javascript function updateLOD() { const distance = Cesium.Cartesian3.distance( viewer.camera.position, clusterPosition); return distance > 10000 ? 'low' : 'high'; } ``` ### 四、高级功能扩展 1. **多级聚合样式**: ```javascript function generateClusterIcon(count) { const size = Math.min(40 + Math.log2(count)*5, 100); return createCanvasIcon(size, getColorByCount(count)); } ``` 2. **交互事件穿透**: ```javascript screenSpaceEventHandler.setInputAction((e) => { const picked = viewer.scene.pick(e.position); if (picked?.id?.clusterId) { handleClusterClick(picked.id.clusterId); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值