CesiumJS 2022^ 原理[3] 渲染原理之从 Entity 看 DataSource 架构 - 生成 Primitive 的过程
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 API
和 Entity API
这套组合实现了复杂、动态空间地理数据格式的接入。
1.1. 高层数据模型的封装 - DataSource API
这个 API 其实是 Entity API
的基础设施,在源码文件夹下就有一个 DataSources/
文件夹专门收纳 Entity API
和 DataSource API
的源代码,可见重要程度之高。
首先,分别看定义在 Viewer
原型链上的两个属性 entities
、dataSourceDisplay
:
Object.defineProperties(Viewer.prototype, {
// ...
dataSourceDisplay: {
get: function () {
return this._dataSourceDisplay;
},
},
entities: {
get: function () {
return this._dataSourceDisplay.defaultDataSource.entities;
},
},
// ...
}
从上面两个 getter 看,EntityCollection
似乎是被 DataSourceDisplay
对象的 defaultDataSource
管辖的;defaultDataSource
是 CustomDataSource
类型的。
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,其它的都会装入DataSourceDisplay
的DataSourceCollection
容器下,譬如GeoJsonDataSource
、CzmlDataSource
等,在文档中搜 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
创建完成后,就把它添加到 Scene
的 PrimitiveCollection
中了,伴随着 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
,塞入 DataSourceDisplay
的 PrimitiveCollection
中(好家伙,套娃是吧);但是这不是重点,重点是在 _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
的可视化。
几何类型有几个比较特殊的,被单独拎出来作为可视化器,就是 PointVisualizer
、PathVisualizer
和 PolylineVisualizer
;其它的都被收入到 GeometryVisualizer
去了。
我就以 GeometryVisualizer
为例,解释可视化器究竟是如何转换 Entity
成 Primitive
的。
2.2. EntityCollection 与 Visualizer 之间的通信 - 事件机制
实际上,CustomDataSource
只是“拥有”EntityCollection
,它让它管辖的 EntityCollection
在 DataSourceDisplay
这个管家中合理地作为一个数据源存在,并不负责监控 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/changed
是 Entity
增删改时的临时保存容器,每次执行 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
回到 GeometryVisualizer
的 update
方法,很容易发现那三个逆序循环在访问 GeometryUpdaterSet
类型的容器,这个容器是 GeometryVisualizer.js
模块内的私有类。
只有在遍历 _addedObjects
时才会创建 GeometryUpdaterSet
,此时新来的 Entity
会传给这个集合。这个集合的左右也比较简单:
- 为新来
Entity
创建所有的几何更新器(这就是性能可能会出现问题的原因之一了) - 为所有的几何更新器注册
geometryChanged
事件的响应函数
这个几何更新器集合创建完后,会存储到 GeometryVisualizer
中,并与 Entity
的 id
作绑定(方便其它两个逆序循环查找)。
4.3. 性能的提升 - Updater 的分批
之所以在 GeometryVisualizer
的 update
方法中还不能创建 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
所需的 Appearance
和 GeometryInstance
仍然没有创建,它将延续到本文的第 5 节中完成。
5. 批次容器完成数据合并 - Primitive 创建
在临门一脚之前,我还是想介绍完批次容器。
5.1. 批次容器的类型与创建
CesiumJS 目前版本提供了若干种批次容器:
DynamicGeometryBatch
:_dynamicBatchStaticOutlineGeometryBatch
:_outlineBatchesStaticGroundGeometryColorBatch
:_groundColorBatchesStaticGroundGeometryPerMaterialBatch
:_groundMaterialBatchesStaticGeometryColorBatch
:_closedColorBatches、_openColorBatchesStaticGeometryPerMaterialBatch
:_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
上的 PrimitiveCollection
(this.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 中的三维物体渲染架构设计就算解读完成。
渲染的细节、三维物体的创建行为、渲染调度优化仍然值得细细挖掘、学习,不过我认为都要基于渲染架构的基础之上。
之后要写的就是三维地球的骨架和皮肤了,就是旋转椭球体和瓦片四叉树设计架构。
CesiumJS 2022^ 原理[3] 渲染原理之从 Entity 看 DataSource 架构 - 生成 Primitive 的过程的更多相关文章
- CesiumJS 2022^ 源码解读[6] - 三维模型(ModelExperimental)新架构
目录 1. ModelExperimental 的缓存机制 1.1. 缓存池 ResourceCache 1.2. 缓存对象的键设计 ResourceCacheKey 2. 三维模型的加载与解析 2. ...
- Vue视图渲染原理解析,从构建VNode到生成真实节点树
前言 在 Vue 核心中除了响应式原理外,视图渲染也是重中之重.我们都知道每次更新数据,都会走视图渲染的逻辑,而这当中牵扯的逻辑也是十分繁琐. 本文主要解析的是初始化视图渲染流程,你将会了解到从挂载组 ...
- CesiumJS 2022^ 源码解读[0] - 文章目录与源码工程结构
很高兴你能在浮躁的年代里还有兴趣阅读源代码,CesiumJS 至今已有十年以上,代码量也积累了三十多万行(未压缩状态). 我也很荣幸自己的文章能被读者看到,如果对你有帮助.有启发,点个赞就是对我最大的 ...
- Web高性能动画及渲染原理(1)CSS动画和JS动画
目录 一. CSS动画 和 JS动画 1.1 CSS动画 1.2 JS动画 1.3 小结 二. 使用Velocity.js实现动画 示例代码托管在:http://www.github.com/dash ...
- 高性能Web动画和渲染原理系列(1)——CSS动画和JS动画
[摘要] 介绍CSS动画和JS动画的基本特点,以及轻量级动画库velocity.js的基本用法. 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园 ...
- CesiumJS 2022^ 原理[2] 渲染架构之三维物体 - 创建并执行指令
目录 回顾 预备知识:指令 预备知识:通道 1. 生成并执行指令 1.1. Primitive 生成指令 1.2. Context 对象负责执行 WebGL 底层代码 2. 多段视锥体技术 3. 指令 ...
- 【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理
承接上一篇:[CSS3进阶]酷炫的3D旋转透视 . 最近入坑 Web 动画,所以把自己的学习过程记录一下分享给大家. CSS3 3D 行星运转 demo 页面请戳:Demo.(建议使用Chrome打开 ...
- react渲染原理深度解析
https://mp.weixin.qq.com/s/aM-SkTsQrgruuf5wy3xVmQ 原文件地址 [第1392期]React从渲染原理到性能优化(二)-- 更新渲染 黄琼 前端早读课 ...
- (转)简述47种Shader Map的渲染原理与制作方法
在Shader中会使用各种不同图参与渲染,所以简单地总结下各种图的渲染原理.制作方法,最后面几种是程序生成图. 1. Albedo 2. Diffuse(Photographic) 从上图可以看出来, ...
随机推荐
- springcloud学习00-开发工具相关准备
用maven构建springcloud项目,目录结构(图片来源:https://blog.csdn.net/qq_36688143/article/details/82755492) 1.maven ...
- AD2019(Altium designer)常用快捷键,使用技巧
测量工具 测量距离:Ctrl+M, 删除测量结果:shift+C, 退出测量:多次右键 设置栅格 G或Ctrl+G 3D查看 按住shift键,再按住鼠标右键移动鼠标,即可在任意角度查看PCB的3D效 ...
- CF786B/CF787D Legacy
题目描述: luogu cf cf 题解: 最短路+线段树优化建图. 考虑本题的边是点->点.段->点和点->段,我们可以建线段树然后拆成入点和出点. 入点:儿子->父亲,边权 ...
- 一个关于wait/notify与锁关系的探究
wait/notify 机制是解决生产者消费者问题的良药.它的核心逻辑是基于条件变量的锁机制处理.所以,它们到底是什么关系?wait()时是否需要持有锁? notify()是否需要持有锁?先说答案:都 ...
- 『现学现忘』Docker基础 — 33、Docker数据卷容器的说明与共享数据原理
目录 1.数据卷容器的说明 2.数据卷容器共享数据原理 3.总结 4.练习:MySQL实现数据共享 1.数据卷容器的说明 (1)什么是数据卷容器 一个容器中已经创建好的数据卷,其它容器通过这个容器实现 ...
- 半吊子菜鸟学Web开发1 --配置开发环境
先说说我自己的情况,我算是一个半吊子菜鸟,对web开发熟练度为0,但是对熟悉C++和Python 所以这里开始记录我学习Web开发的历程,看看我这里学习的程序,能够学到什么地方. 首先是配置环境,我的 ...
- Spring根据路径前缀获取不同Resource
相关文章:https://www.jianshu.com/p/5bab9e03ab92 官方文档:https://docs.spring.io/spring/docs/current/spring-f ...
- Django的多数据库与读写分离
1.多个数据库 settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.pa ...
- Spring系列28:@Transactional事务源码分析
本文内容 @Transactional事务使用 @EnableTransactionManagement 详解 @Transactional事务属性的解析 TransactionInterceptor ...
- Linux文件管理 | Linux 文件基础知识
目录 Linux 常用文件类别 Linux 目录结构概述 LInux 系统目录及说明 一.Linux 常用文件类别 1.文件 在Linux上系统上,有一切皆文件的说法,就是说任何软件和I/O设备都 ...