Cesium 浅谈Entity机制

Cesium 中官方提供 Entity api 在创建图元时给开发者提供了一种便捷的方式,避免了直接与 Primitive 直接接触。这样能够让刚开始接触这类webgl框架的开发者能够快速上手。这一切的便利都建立在 Cesium 复杂的 Entity 机制中,本篇文章将深入浅出的刨析这背后的运作原理,帮助大家更好的理解。
ps: 本文仅介绍关于 Entity 相关知识,关于如何渲染请参考 Primitive 篇。

 const entity = viewer.entities.add(
        new Cesium.Entity({
            name: item.id,
            polygon: {
                hierarchy: Cesium.Cartesian3.fromDegreesArray(points),
                extrudedHeight: 100,
                material: Cesium.Color.WHITE
            }
        })
)

以上是我们通过 Entity API 创建一个多边形的代码
首先我们提出几点疑问,带着疑问去看源码能够更加深入的帮助我们去理解

  1. 在创建阶段 Cesium 为我们做了哪些工作
  2. Cesium 是如何将 Entity 转换成 Primitive
  3. Cesium 是如何做到当我们修改 entity.polygon 属性时自动更新的

在了解这些问题之前,我们先需要对 DataSource 机制进行一个大致的认识:
Cesium.DataSource 作为一个抽象类,并不能直接被初始化,但是他的一些派生类是我们经常使用到的,例如 GeoJsonDataSourceKmlDataSources。他们可以简单的被理解成是一个图形的集合,下面存放了各种的 Entity,例如我们所熟悉的 viewer.entities 就是 viewer.dataSourceDisplay.defaultDataSource.entities 的一个快捷访问属性,后者是 Cesium.CustomDataSource 的一个实例。
所有的 DataSource 被管理在 viewer.datasources中,它是 DataSourceCollection 的一个实例,它仅负责管理各个 DataSource,可以理解成一个容器,提供addremovecontain等方法,具体负责对它们进行更新的则是 viewer.dataSourceDisplay,这个类在初始化Viewer的时候会被创建,在这个过程中主要做了以下工作

  1. 创建 PrimitiveCollection 容器,负责存放所有entity被转换后的primitive,同时将他们合并到scene.primitives中,参与到渲染
  const primitives = new PrimitiveCollection();
  const groundPrimitives = new PrimitiveCollection();
  if (dataSourceCollection.length > 0) {
    scene.primitives.add(primitives);
    scene.groundPrimitives.add(groundPrimitives);
    primitivesAdded = true;
  }
  1. 注册一个事件,用于监听当有新的 DataSource 被添加到 viewer.datasources 时,给这些新添加的 DataSource 创建子容器 PrimitiveCollection并将它们挂在到DataSource._primitives。于此同时更加重要的是给 DataSource 创建一堆 Visualizer 这才是背后真正发挥作用的

代码太长就不粘贴了,具体参考 DataSourceDisplay.prototype._onDataSourceAdded、DataSourceDisplay.defaultVisualizersCallback

  1. 创建 CustomDatasource
  const defaultDataSource = new CustomDataSource();
  this._onDataSourceAdded(undefined, defaultDataSource);
  this._defaultDataSource = defaultDataSource;

完成了对 DataSource 机制的一个介绍,接下来正式介绍创建 Entity 的过程,当调用 datasources.entities(EntityCollection)add 方法时会触发以下事件,如下

function fireChangedEvent(collection) {
  ...省略
        collection._collectionChanged.raiseEvent(
          collection,
          addedArray,  //本次变动新增的entity
          removedArray,  //本次变动移除的entity
          changedArray  //本次变动修改的entity
        );
   ...省略
}

_collectionChanged事件早在创建各个Visualizer就已经被监听,例如在GeometryVisualizer.constructor的最后

  entityCollection.collectionChanged.addEventListener(
    GeometryVisualizer.prototype._onCollectionChanged,
    this
  );

因此这一动作就被各个Visualizer所捕获,继而它们内部对新增/移除的Entity进行处理,具体代码参考Visualizer.prototype._onCollectionChanged,简单一句话介绍就是将这些 entity 放入各自的缓存中①,等待下一轮的 update

至此我们解答了第一个问题,着重需要额外提到的是在各个 Visualizer 的初始化阶段,创建了一系列的 Batch 容器,它们将帮助这些 entity 实现合批渲染

接下来我们需要着重介绍一下Cesium是怎么将Entity转换到Primitive的这一过程
从源码中我们可以得知,这一行为发生在update阶段。viewer在创建阶段注册了 _onTick事件,每次的更新会触发该事件,继而触发DataSourceDisplay的update方法。在该update方法中会遍历所有DataSource,触发他们的Visualizer,重点来了:
在这些 Visualizer ( 这里以GeometryVisualizer举例 ) 的update方法中会对缓存中的entity ①进行处理:

  • 新增
  • 修改
  • 移除

这里以新增为例:

  for (i = added.length - 1; i > -1; i--) {
    entity = added[i];
    id = entity.id;
    updaterSet = new GeometryUpdaterSet(entity, this._scene);  //创建Updater
    this._updaterSets.set(id, updaterSet);
    updaterSet.forEach(function (updater) {
      that._insertUpdaterIntoBatch(time, updater);  //分批
    });
    this._subscriptions.set(
      id,
      updaterSet.geometryChanged.addEventListener(
        GeometryVisualizer._onGeometryChanged,
        this
      )
    );
  }

这里有几个重要的操作:

  1. 创建 UpdaterSet,这里面包含了各种的Updater,不管在 entity 中是否声明了对应的 geometry 都会创建,这也是常说 Entity 性能会略差的原因,包括之前创建的各种 Visualizer。不过 Entity 强大之处在于极大的简化了开发者的工作量,包括后面的合批渲染。

Updater 在创建阶段会根据 entity 中声明对应的属性进行填充,例如:声明entity.box属性,那么在BoxGeometryUpdater 中会自动从我们声明的内容中提取,没有声明的则取默认值。每个 updater 提供一个 createFillGeometryInstance方法,负责创建 geometryInstance

  1. 创建好 Updater 以后,接下来就是根据以下状态将他们放入不同的批次(Batch):
  • material 属性
  • closeTop / closeBottom 属性
  • outline 属性
    这次批次被保存在一系列的批次容器中,这类批次容器有几大类:
StaticOutlineGeometryBatch
StaticGeometryColorBatch
StaticGeometryPerMaterialBatch

在初始化GeometryVisualizer阶段时就已经对这些容器进行创建

  this._outlineBatches = new Array(numberOfShadowModes * 2);
  this._closedColorBatches = new Array(numberOfShadowModes * 2);
  this._closedMaterialBatches = new Array(numberOfShadowModes * 2);
  this._openColorBatches = new Array(numberOfShadowModes * 2);
  this._openMaterialBatches = new Array(numberOfShadowModes * 2);

省略后面的一系列new 操作

被放入同一批次的 entity 会被合并到一个Primitve

  1. 监听 Updater 的 geometryChanged 事件,当他们被触发时意味着这个entity发生了改动,因此需要将他们放入到changedObjects队列,等待下一次update时将他们重新分批
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容