关于cesium:CesiumJS-2022^-原理3-渲染原理之从-Entity-看-DataSource-架构

34次阅读

共计 14681 个字符,预计需要花费 37 分钟才能阅读完成。

API 用法回顾

只需传入参数对象,就能够简略地创立三维几何体或者三维模型。

const modelEntity = viewer.entites.add({
  id: 'some-entitiy',
  name: 'some-name',
  position: Cartesian3.fromDegrees(112.5, 22.3, 0),
  model: {uri: 'path/to/model.glb'}
})

Entity API 通常会被拿来与 Primitive API 比拟,无外乎:

  • 前者应用 Property API 使得动态效果简单化,后者须要本人编写着色器;
  • 个体数量较多时,前者的性能不如后者;
  • 后者反对较底层的用法,能够本人管制材质着色器、几何数据并批优化;

本篇感兴趣的是 Entity API 是如何从参数化对象到 WebGL 渲染的。

首先,上论断:Entity 最终也会变成 Primitive

从下面简略的示例代码能够看出,应用 Entity API 的入口是 Viewer,它不像 Primitive API 是从 Scene 拜访的。

这正是对于 Entity API 源代码和设计架构的第一个常识,Entity API 必须依赖 Viewer 容器。

前提是只用公开出来的 API

1. 为什么要从 Viewer 拜访 Entity API

Viewer 其实是 CesiumJS 长期保护的一个成绩,它在大多数时候表演的是 Web3D GIS 地球的总入口对象。明天的配角是它裸露进去的 Entity API,不过在介绍它之前,还要再提一提 Scene 裸露进去的 Primitive API

Scene 裸露进去的 Primitive API 是一种比拟靠近 WebGL 数据接口的 API,面对靠近业务层的数据格式,譬如 GeoJSON、KML、GPX 等,Primitive API 就略显吃力了。

尽管能够做一些转换接口,不过 Cesium 团队联合本人研发的数据标记语言 — CZML,配上内置的时钟,封装出了更高级别的架构。

CesiumJS 应用 DataSource APIEntity API 这套组合实现了简单、动静空间天文数据格式的接入。

1.1. 高层数据模型的封装 – DataSource API

这个 API 其实是 Entity API 的基础设施,在源码文件夹下就有一个 DataSources/ 文件夹专门收纳 Entity APIDataSource API 的源代码,可见重要水平之高。

首先,别离看定义在 Viewer 原型链上的两个属性 entitiesdataSourceDisplay

Object.defineProperties(Viewer.prototype, {
  // ...
  dataSourceDisplay: {get: function () {return this._dataSourceDisplay;},
  },
  entities: {get: function () {return this._dataSourceDisplay.defaultDataSource.entities;},
  },
  // ...
}

从下面两个 getter 看,EntityCollection 仿佛是被 DataSourceDisplay 对象的 defaultDataSource 管辖的;defaultDataSourceCustomDataSource 类型的。

Viewer 领有一个 DataSourceDisplay 成员,它负责所有 DataSource 的更新。接下来先介绍这个“显示管理器”类。

1.2. 显示管理器 DataSourceDisplay 与默认数据源 CustomDataSource

它随 Viewer 创立而创立,而且优先级相当高,仅次于 CesiumWidget;它本人则创立默认的 DataSource,也就是 CustomDataSource

// DataSourceDisplay.js
function DataSourceDisplay(options) {
  // ...
  const defaultDataSource = new CustomDataSource();
  this._onDataSourceAdded(undefined, defaultDataSource);
  this._defaultDataSource = defaultDataSource;
  // ...
}

在这个 CustomDataSource 的构造函数里,就能找到 Viewer 裸露进来的 EntityCollection

// CustomDataSource.js
function CustomDataSource(name) {
  // ...
  this._entityCollection = new EntityCollection(this);
  // ...
}

Object.defineProperties(CustomDataSource.prototype, {
  // ...
  entities: {get: function () {return this._entityCollection;},
  },
  // ...
}

所以,蕴含关系就说分明了:

Viewer
┖ DataSourceDisplay
  ┖ CustomDataSource
    ┖ EntityCollection

DataSourceDisplay 除了管着 CustomDataSource 这个服务于 Entity API 的默认数据源外,还管着其它的 DataSource,其它的都会装入 DataSourceDisplayDataSourceCollection 容器下,譬如 GeoJsonDataSourceCzmlDataSource 等,在文档中搜 DataSource 关键字根本能找齐。

1.3. 默认的数据源 – CustomDataSource

默认的数据源的作用,就是给 Entity API 提供土壤。

然而不要轻易认为 CustomDataSource 只能给 Entity API 应用,在官网沙盒中能够找到间接应用 CustomDataSource 的例子的。本文

1.4. DataSource API 与 Scene 之间的桥梁

文章一结尾就说了,Entity 最终是会转换成 Primitive 的。

目前为止,CesiumJS 有更新 Primitive 势力的对象,只有 Scene 上那个 PrimitiveCollection 能力更新 Primitive,进而创立 DrawCommand

DataSource API 的管家是 DataSourceDisplay 对象,它领有一个公有的 PrimitiveCollection 成员:

function DataSourceDisplay(options) {
  // ...
  const scene = options.scene;
  const dataSourceCollection = options.dataSourceCollection;
  // ...
  
  let primitivesAdded = false;
  const primitives = new PrimitiveCollection();
  const groundPrimitives = new PrimitiveCollection();
  
  if (dataSourceCollection.length > 0) {scene.primitives.add(primitives);
    scene.groundPrimitives.add(groundPrimitives);
    primitivesAdded = true;
  }
  
  this._primitives = primitives;
  this._groundPrimitives = groundPrimitives;
  
  // ...
  
  if (!primitivesAdded) {
    // 对于 dataSourceCollection.length 是 0 的状况
    // 应用事件机制把公有的 PrimitiveCollection 增加到 scene.primitives 中
  }
}

看失去,这个公有的 PrimitiveCollection 创立实现后,就把它增加到 ScenePrimitiveCollection 中了,随同着 CesiumWidget 调度的渲染循环进行帧渲染。

而这个公有的 PrimitiveCollection 通过层层传递,会传递到最终负责创立 Primitive 的办法中(负责 Entity 以后时刻的 Primitive 的 API 在最初一大节会提及,别急)

PrimitiveCollection 反对嵌套增加,也就是 Collection 能够增加到 Collection 中,update 时也会树状逐级向下更新。

2. 负责 DataSource API 可视化的一线员工 – Visualizer

2.1. 为 CustomDataSource 创立 Visualizer

留神到 DataSourceDisplay 创立 defaultDataSource 时,它会被动调用 _onDataSourceAdded 办法:

// function DataSourceDisplay() 中
const defaultDataSource = new CustomDataSource();
this._onDataSourceAdded(undefined, defaultDataSource);
this._defaultDataSource = defaultDataSource;

这个办法会给 defaultDataSource 再创立一个公有的 PrimitiveCollection,塞入 DataSourceDisplayPrimitiveCollection 中(好家伙,套娃是吧);然而这不是重点,重点是在 _onDataSourceAdded 办法中会紧接着调用 _visualizersCallback 办法创立 可视化器(Visualizer)

// DataSourceDisplay.prototype._onDataSourceAdded 中
dataSource._visualizers = this._visualizersCallback(
  scene,
  entityCluster,
  dataSource
);

_visualizersCallback 办法是 DataSourceDisplay 的一个公有原型链上的办法,能够在创立时自定义。简略起见,就当默认状况探讨吧,默认状况用的是 DataSourceDisplay 类的静态方法:

function DataSourceDisplay(options) {
  // ...
  this._visualizersCallback = defaultValue(
    options.visualizersCallback,
    DataSourceDisplay.defaultVisualizersCallback
  );
  // ...
}

DataSourceDisplay.defaultVisualizersCallback = function (
  scene,
  entityCluster,
  dataSource
) {
  const entities = dataSource.entities;
  return [new BillboardVisualizer(entityCluster, entities),
    new GeometryVisualizer(
      scene,
      entities,
      dataSource._primitives,
      dataSource._groundPrimitives
    ),
    new LabelVisualizer(entityCluster, entities),
    new ModelVisualizer(scene, entities),
    new Cesium3DTilesetVisualizer(scene, entities),
    new PointVisualizer(entityCluster, entities),
    new PathVisualizer(scene, entities),
    new PolylineVisualizer(
      scene,
      entities,
      dataSource._primitives,
      dataSource._groundPrimitives
    ),
  ];
};

静态方法是 ES6 Class 的说法,CesiumJS 作为一套 ES5 时代的源码,大家意会即可。这个办法会返回一个数组,数组内是一堆 Visualizer 对象。

每个 Visualizer 就负责一类 Entity 的具体可视化工作,譬如 ModelVisualizer 负责 glTF 模型类型的 Entity 的可视化工作,Cesium3DTilesetVisualizer 负责 3DTiles 数据集类型的 Entity 的可视化。

几何类型有几个比拟非凡的,被独自拎进去作为可视化器,就是 PointVisualizerPathVisualizerPolylineVisualizer;其它的都被支出到 GeometryVisualizer 去了。

我就以 GeometryVisualizer 为例,解释可视化器到底是如何转换 EntityPrimitive 的。

2.2. EntityCollection 与 Visualizer 之间的通信 – 事件机制

实际上,CustomDataSource 只是“领有”EntityCollection,它让它管辖的 EntityCollectionDataSourceDisplay 这个管家中正当地作为一个数据源存在,并不负责监控 Entity 的变动(增删改)。

真正监听 Entity 变动的是通过 EntityCollection 的事件机制实现的,EntityCollection 无论产生什么变动,都会传递给 Visualizer,图解如下:

DataSourceDisplay
┖ CustomDataSource
  ┠ EntityCollection
  ┃      ↑
  ┃  事件机制监听变动
  ┃      |
  ┖ [Visualizers]

接下来看看代码中的实现。EntityCollection 原型链上的 add/removeById/removeAll 办法会执行一个模块内的函数 fireChangedEvent(),它最外围的作用,就是把减少、删除、批改的 Entity 通过事件触发告诉给 Visualizer:

// function fireChangedEvent() 中
const addedArray = added.values.slice(0);
const removedArray = removed.values.slice(0);
const changedArray = changed.values.slice(0);

added.removeAll();
removed.removeAll();
changed.removeAll();
collection._collectionChanged.raiseEvent(
  collection,
  addedArray,
  removedArray,
  changedArray
);

其中,added/removed/changedEntity 增删改时的长期保留容器,每次执行 fireChangedEvent 函数时都会把这三个容器革除。

在下面这段代码中,触发事件的还是 EntityCollection 自身,fireChangedEvent 只是把变动的、最新那个 Entity 取出并告诉注册的回调。

Visualizer 在创立的时候,就给 EntityCollection 注册了事件:

// 在 GeometryVisualizer 的构造函数中
entityCollection.collectionChanged.addEventListener(
  GeometryVisualizer.prototype._onCollectionChanged,
  this
);

这就是说,每当 EntityCollection 有增删改变动时,GeometryVisualizer_onCollectionChanged 就会收到变动的 Entity,并继续执行后续动作。

Entity 的属性批改是借助 Property API 实现的,它增加到 EntityCollection 时(add 办法),容器就会为该 Entity 注册属性变动事件的回调:

// EntityCollection.prototype.add 中
entity.definitionChanged.addEventListener(
  EntityCollection.prototype._onEntityDefinitionChanged,
  this
);

_onEntityDefinitionChanged 在 Entity 的 definitionChanged 事件触发后执行,即也是执行 fireChangedEvent 函数。

3. 时钟 – 如何让 Viewer 参加 CesiumWidget 的渲染循环

在前两篇文章中,具体解析了 CesiumWidget 是如何调度 Scene 的帧渲染的。

CesiumWidget 领有一个时钟成员:

// CesiumWidget 构造函数中
this._clock = defined(options.clock) ? options.clock : new Clock();

默认的时钟会在每一帧渲染调度函数中 跳动

CesiumWidget.prototype.render = function () {if (this._canRender) {this._scene.initializeFrame();
    const currentTime = this._clock.tick();
    this._scene.render(currentTime);
  } else {this._clock.tick();
  }
};

无论是否渲染,都会调用 Clock.prototype.tick() 办法跳动一次时钟,这个办法会触发 onTick 事件:

Clock.prototype.tick = function () {
  // ...
  this.onTick.raiseEvent(this);
  // ...
}

也就是这个重要的时钟,让 Viewer 通过事件机制参加了 CesiumWidget 调度的渲染循环。

Viewer 在构造函数中,先创立了 CesiumWidget,随后就为时钟注册了 onTick 的回调函数:

function Viewer(container, options) {
  // ...
  // eventHelper 是一个事件助手对象,此处为 clock 注册事件用
  eventHelper.add(clock.onTick, Viewer.prototype._onTick, this);
  // ...
}

Viewer.prototype._onTick = function (clock) {
  const time = clock.currentTime;

  const isUpdated = this._dataSourceDisplay.update(time);
  // ...
}

_onTick 办法中,第一件做的事件就是执行 DataSourceDisplay 的更新:

DataSourceDisplay.prototype.update = function (time) {
  // ...
  let result = true;
  
  let visualizers;
  let vLength;
  
  visualizers = this._defaultDataSource._visualizers;
  vLength = visualizers.length;
  for (x = 0; x < vLength; x++) {result = visualizers[x].update(time) && result;
  }
  
  // ...
}

这个更新办法其实就是 进一步更新 DataSourceDisplay 中所有的数据源(无论是数据源容器中的还是默认的 CustomDataSource 的)的 可视化器(Visualizer),可视化器在上一节曾经介绍过它的创立和如何与 EntityCollection 绑定的了。


待介绍完各个层级的数据容器创立、事件的绑定后,终于能够把眼光聚焦在渲染上了。

CesiumWidget 负责调度 Scene 的帧渲染,同时会跳动时钟对象,时钟对象的跳动又进而告诉 Viewer 更新 DataSourceDisplay 下辖的所有 DataSource。

到这里,各个数据源对象的 Visualizer 才开始了创立 Primitive 之路。

4. Visualizer 的更新之路

4.1. 更新办法中的三个循环

仍以 GeometryVisualizer 为例。接续第 3 节的内容,Viewer 随同着时钟对象的回调,会一路更新数据源对象的 Visualizer。

看看 GeometryVisualizer 的更新办法:

GeometryVisualizer.prototype.update = function (time) {
  // ...
  const addedObjects = this._addedObjects;
  const added = addedObjects.values;
  const removedObjects = this._removedObjects;
  const removed = removedObjects.values;
  const changedObjects = this._changedObjects;
  const changed = changedObjects.values;
  
  let i;
  let entity;
  let id;
  let updaterSet;
  const that = this;
  
  for (i = changed.length - 1; i > -1; i--) {/* ... */}
  for (i = removed.length - 1; i > -1; i--) {/* ... */}
  for (i = added.length - 1; i > -1; i--) {/* ... */}
  
  addedObjects.removeAll();
  removedObjects.removeAll();
  changedObjects.removeAll();
  
  // ...
}  

更新办法会取三类 Entity_addedObjects/_removedObjects/_changedObjects)进行逆序遍历,这三个容器在 2.2 大节中会通过 EntityCollection 的事件机制传递给 Visualizer。

遍历这些 Entity 是打算做什么呢?Entity 这个时候依然是参数对象,还不能间接拿去创立 Primitive。在探讨为什么之前,先介绍两个货色,见 4.1 和 4.2:

4.1. Visualizer 的数据转换工具 – Updater

咱们晓得,Entity 应用 Property API 去批改实体的形态、外观,而这些动静值每一帧必须变成动态值传递给 WebGL,Entity 中的几何类型不少,CesiumJS 别离给这些几何类型的动静转动态的过程做了封装 —— 也就是叫做 Updater 的货色,来辅助几何类型的 Entity 的几何数据更新。

GeometryVisualizer.js 文件靠前的地位,你能够找到一个数组:

const geometryUpdaters = [
  BoxGeometryUpdater,
  CylinderGeometryUpdater,
  CorridorGeometryUpdater,
  EllipseGeometryUpdater,
  EllipsoidGeometryUpdater,
  PlaneGeometryUpdater,
  PolygonGeometryUpdater,
  PolylineVolumeGeometryUpdater,
  RectangleGeometryUpdater,
  WallGeometryUpdater,
];

这些就是对应的几何更新器。

你能够在这些几何更新器类中找到 createXXXGeometryInstance 的原型链上的办法,例如 EllipsoidGeometryUpdater.prototype.createFillGeometryInstance 办法。

这些办法就是最初创立 Primitive 时所需的 GeometryInstance 的创建者,它们依赖于工夫,返回该工夫的动态几何值。

4.2. Updater 的汇合 – GeometryUpdaterSet

回到 GeometryVisualizerupdate 办法,很容易发现那三个逆序循环在拜访 GeometryUpdaterSet 类型的容器,这个容器是 GeometryVisualizer.js 模块内的公有类。

只有在遍历 _addedObjects 时才会创立 GeometryUpdaterSet,此时新来的 Entity 会传给这个汇合。这个汇合的左右也比较简单:

  • 为新来 Entity 创立所有的几何更新器(这就是性能可能会呈现问题的起因之一了)
  • 为所有的几何更新器注册 geometryChanged 事件的响应函数

这个几何更新器汇合创立完后,会存储到 GeometryVisualizer 中,并与 Entityid 作绑定(不便其它两个逆序循环查找)。

4.3. 性能的晋升 – Updater 的分批

之所以在 GeometryVisualizerupdate 办法中还不能创立 Primitive,只管 CesiumJS 曾经把创立动态几何值的行为封装在 4.1 和 4.2 中提到的几何更新器中了,是因为波及一个性能问题:几何并批。

WebGL 的特点就是,单帧内绘制的次数越少,就越晦涩。GeometryVisualizer 如果不为这些承受来的 Entity 分类归并批次,而是粗犷地把每个 Entity 间接生成动态几何、外观数据就创立 Primitive 的话,有多少 Entity 就会有多少 Primitive,也就有多少 DrawCommand,性能可见会十分蹩脚。

CesiumJS 在 GeometryVisualizer 中设计了一个分批的过程,也就是原型链上的 _insertUpdaterIntoBatch 办法。

GeometryVisualizer 更新时,三个列表循环中的两个(增加列表和更改列表)都会调用 _insertUpdaterIntoBatch 办法,把因为新增或批改 Entity 而创立进去的新的 Updater 做分批。

GeometryVisualizer.prototype.update = function (time) {
  // ...
  for (i = changed.length - 1; i > -1; i--) {
    // ...
    that._insertUpdaterIntoBatch(time, updater);
  }
  
  // ...
  
  for (i = added.length - 1; i > -1; i--) {
    // ...
    that._insertUpdaterIntoBatch(time, updater);
    // ...
  }
  
  // ...
}

而在 _insertUpdaterIntoBatch 办法中,能看到十分多的分支判断以及 add 操作,这就是将 Updater 依据不同的条件甩到 Visualizer 上不同的批次容器中的过程了。

对于批次容器,会在第 5 节中解说。

4.4. Visualizer 更新的最初一步 – 批次容器更新

待 Visuailzer 更新办法的三个循环完结后,也就意味着实现了 Updater 的分批。

Updater 分批实现后,天然就是更新这些批次容器,进而创立出以后时刻的 Primitive,让他们期待 Scene 的渲染了:

GeometryVisualizer.prototype.update = function (time) {
  // ...
  
  let isUpdated = true;
  const batches = this._batches;
  const length = batches.length;
  for (i = 0; i < length; i++) {isUpdated = batches[i].update(time) && isUpdated;
  }

  return isUpdated; 
}

直到这时,Primitive 所需的 AppearanceGeometryInstance 依然没有创立,它将连续到本文的第 5 节中实现。

5. 批次容器实现数据合并 – Primitive 创立

在临门一脚之前,我还是想介绍完批次容器。

5.1. 批次容器的类型与创立

CesiumJS 目前版本提供了若干种批次容器:

  • DynamicGeometryBatch:_dynamicBatch
  • StaticOutlineGeometryBatch:_outlineBatches
  • StaticGroundGeometryColorBatch:_groundColorBatches
  • StaticGroundGeometryPerMaterialBatch:_groundMaterialBatches
  • StaticGeometryColorBatch:\_closedColorBatches、\_openColorBatches
  • StaticGeometryPerMaterialBatch:\_closedMaterialBatches、\_openMaterialBatches

下面列出的,前者是类型,冒号前面的是 Visualizer 的成员字段(也就是具体批次容器对象),从名称不难看出它们的不同之处,大部分是用材质或色彩来作为分类根据。

上述批次容器能够在 DataSources/ 文件夹中找到对应的模块以及导出的类。

你能够在 GeometryVisualizer 的构造函数中找到创立这些成员字段的代码(其实构造函数里大部分代码也是在创立批次容器)。它们最终会合并到 _batches 数组中不便遍历:

this._batches = this._outlineBatches.concat(
  this._closedColorBatches,
  this._closedMaterialBatches,
  this._openColorBatches,
  this._openMaterialBatches,
  this._groundColorBatches,
  this._groundMaterialBatches,
  this._dynamicBatch
);

5.2. 外部批次容器

没想到吧?下面列举的,名字上应用材质或色彩来辨别的批次容器,还只是一个代理人。真正起存储作用的,还得看这些批次容器模块文件中外部的 Batch 类。

以最简略的动态批次容器 StaticGeometryColorBatch 为例,它在 Updater 通过 add 办法增加进来时,就会创立外部 Batch,同时创立这个时刻的 GeometryInstance

// StaticGeometryColorBatch.js

function Batch(
  primitives,
  translucent,
  appearanceType,
  depthFailAppearanceType,
  depthFailMaterialProperty,
  closed,
  shadows
) {// ...}

StaticGeometryColorBatch.prototype.add = function (time, updater) {
  // ...
  const instance = updater.createFillGeometryInstance(time);
  // ...
  
  const batch = new Batch(/* ... */);
  batch.add(updater, instance);
  items.push(batch);
}

这个外部 Batch 寄存着外观信息和 GeometryInstance 对象。

5.3. 创立 Primitive

在 Visualizer 的更新办法中,最初就是对所有批次容器进行更新。仍以 StaticGeometryColorBatch 为例,它的更新办法会调用一个模块内的 updateItems 函数,这个函数对传入的某局部外部 Batch 执行更新:

// StaticGeometryColorBatch.js 中

function updateItems(batch, items, time, isUpdated) {
  // ...
  for (i = 0; i < length; ++i) {isUpdated = items[i].update(time) && isUpdated;
  }
  // ...
}

StaticGeometryColorBatch.prototype.update = function (time) {
  // ...
  if (solidsMoved || translucentsMoved) {
    isUpdated =
      updateItems(this, this._solidItems, time, isUpdated) && isUpdated;
    isUpdated =
      updateItems(this, this._translucentItems, time, isUpdated) && isUpdated;
  }
  // ...
}

StaticGeometryColorBatch 上的 _solidItems_translucentItems 都是一般的数组,保留的是模块外部定义 Batch 类型的对象。

而这些外部 Batch 的更新函数,最终就会依据手上的材料,实现 Primitive 的创立:

// StaticGeometryColorBatch.js 中

// ... 这个办法很长,节约篇幅
Batch.prototype.update = function (time) {
  let isUpdated = true;
  let removedCount = 0;
  let primitive = this.primitive;
  const primitives = this.primitives;
  let i;
  
  if (this.createPrimitive) {
    const geometries = this.geometry.values;
    const geometriesLength = geometries.length;
    if (geometriesLength > 0) {
      // ...
      primitive = new Primitive({/* ... */})
      primitives.add(primitive);
    } // else ...
  } // else ...
}

而这个内置 Batch 上的 PrimitiveCollectionthis.primitives),则是由 CustomDataSource ~ GeometryVisualizer ~ StaticGeometryColorBatch 一路传下来的,它早已在本文 1.4 大节中提及。

至此,Entity 终于穿过九曲十八弯,实现了动态 Primitive 的创立,终于能够把事件交给 Scene 持续做了,期待 Scene 在帧渲染流程中更新 PrimitiveCollection 进而创立出 DrawCommand,期待 WebGL 绘制。

最初,补个关系图:

Viewer
┖ DataSourceDisplay
  ┖ CustomDataSource
    ┠ EntityCollection
    ┃      ↑
    ┃  事件机制监听变动
    ┃      |
    ┖ GeometryVisualizer
      ┠ GeometryUpdaterSet
      ┃ ┖ [Updaters]
      ┃      ┃
      ┃    ┎─┸─ 创立→ Primitive
      ┃    ┃
      ┖ [Batches]

本篇小结

我原本是想写 Entity API 的设计架构的,然而为了弄清楚这个比渲染循环简单得多的架构(次要是事件回调机制到处交叉,显得简单),我做了很多细碎的文章片段,最初收拢在一起的时候,才挖出 CesiumJS 中 DataSource 这套高层级的数据模型的架构设计。

尽管 Entity API 从参数化 JavaScript 对象到 Scene + Primitive API 这一层的路线比拟长,然而易用性进步却是事实。

Scene + Primitive API 作为基底,自身是比拟高效率的,也留下了自定义的入口。Viewer + DataSource/Entity API 更进一步,使得 CesiumJS 更易于简略业务的实现。

我感觉写完几何类型的 Entity 渲染架构,就算点到为止了(其它类型的 Entity 有专属的 Visualizer,请读者带着几何类型的 Entity 的思路类比),CesiumJS 中的三维物体渲染架构设计就算解读实现。

渲染的细节、三维物体的创立行为、渲染调度优化依然值得细细开掘、学习,不过我认为都要基于渲染架构的根底之上。

之后要写的就是三维地球的骨架和皮肤了,就是旋转椭球体和瓦片四叉树设计架构。

正文完
 0