Cesium原理篇:7最长的一帧之Entity(上)
之前的最长的一帧系列,我们主要集中在地形和影像服务方面。简单说,之前我们都集中在地球是怎么造出来的,从这一系列开始,我们的目光从GLOBE上解放出来,看看球面上的地物是如何渲染的。本篇也是先开一个头,讲一下涉及到的类结构和整体的流程,有一个系统的,概括的理解。
我们先看看Cesium的渲染队列:
var Pass = {
// 环境,比如大气层,月亮,天空盒等
ENVIRONMENT : 0,
//之前介绍的ComputeEngine,比如影像服务里面的投影涉及的计算
COMPUTE : 1,
// 地球切片
GLOBE : 2,
// 贴地的Geometry
GROUND : 3,
// 不透明的Geometry
OPAQUE : 4,
// (半)透明的Geometry
TRANSLUCENT : 5,
OVERLAY : 6,
NUMBER_OF_PASSES : 7
};
如上是Cesium的渲染队列,也是Cesium渲染的优先级,非常的清晰。就像画家作画一样,我们先渲染地球,这样就有了一个落脚地(同时也有了深度纹理和模版缓冲区等),然后再画贴地的地物,然后是那些不贴地的且不透明的地物,接着是哪些透明度的地物,这时之前渲染地物的颜色和深度缓冲区,可以方便的进行深度计算和颜色的叠加,最上面的则是Overlay,比如一些公告板。这样一层层的渲染,建立起整个场景的层次感和立体感。
Geometry的难点在于种类繁多,材质类型也不少,技术上如何高效的渲染,根据是否贴地,是否透明等属性来设定渲染的优先级,根据材质和Geometry的类型来打组和实例化。同时Geometry的Type也是多样的,如何调度,并且将参数化的Geometry高效的分解为VBO三角面片,里面的水确实很深。万事开头难,我们先从简单的说起。
var entities = viewer.entities; entities.add({
rectangle : {
coordinates : Cesium.Rectangle.fromDegrees(-92.0, 20.0, -86.0, 27.0),
outline : true,
outlineColor : Cesium.Color.WHITE,
outlineWidth : 4,
stRotation : Cesium.Math.toRadians(45),
}
});
这是Cesium提供的添加Geometry的示例代码。So easy,不是吗。Viewer中提供了entities,是一个EntityCollection类,用户创建的不同类型的Geometry,都可以通过add方法添加,其他的就交给Cesium,最终实现屏幕渲染。顺藤摸瓜,我们先列举出相关的类,以及它们之间关系草图:
Viewer
DataSourceDisplay
DataSourceCollection
CustomDataSource
EntityCollection
Entity
先不关注具体的细节,不难理解,当我们调用EntityCollection.add方法时,根据参数会new一个Entity对象,此时EntityCollection充当了一个容器的作用。接着,Cesium内部通过事件的机制,在DataSourceDisplay中根据Entity类型安排具体模块,最终该模块完成对应Entity的解析工作。这里你就会发现,在这个通讯过程中,EntityCollection和DataSourceDisplay是直接进行消息传递的,直接绕开了CustomDataSource,那DataSource模块又有什么鸟用,岂不是站着坑不干活。下面通过一个例子让大家有一个形象了解。
首先,作为一名热爱编码的程序员而言,是否曾经仰天长叹,为什么那个谁谁谁天天不干活,工资还比我高,我不明白。想想自家公司那些占坑不干活的人都是什么角色——管理人员。CustomDataSource并没有参与实际的工作中,当状态变化时,维护一下EntityCollection和DataSourceDisplay两者的关联,保证他们之间消息的畅通。比如初始化时,CustomDataSource把EntityCollection带到DataSourceDisplay处,抛下一句话,以后有什么事情就找他,多多联系,然后立马就闪人了。这时,一脸懵逼的EntityCollection说,我得赶紧接客户去了,要不留个联系方式,有什么事情我好通知你。DataSourceDisplay也是管理层,自然不肯交出手机号,立马找到助理(Visualizer),抛下一句话“以后你俩直接联系”。这样,EntityCollection和Visualizer的通讯渠道就建立起来了。Visualizer我们后面会介绍,这里略过。
DataSourceCollection的作用也大致如此,但不同于CustomDataSource主要是接散户,消极怠慢。DataSourceCollection主要针对大客户或集团客户,比如GeoJson,KML,CZML等,它就不敢怠慢了,根据每一种情况做好功课,熟悉每一个集团的特点和特殊需要,提供专业高效的服务。通过上面的例子我们大概明白为什么要增加DataSource这一层,来方面管理,通过分层来优化,消化异常情况,尽量保证EntityCollection和DataSourceDisplay之间的交流是标准化,才能保证它们的通畅。针对这一方面,在后面的章节中在详细讨论,我们还是把注意力放在在整个流程上。
如上主要是介绍了人事关系,现在我们再来看看业务流程。大家都有去银行办理业务的经历,这里DataSourceDisplay相当于这个银行大厅的大厅经理,主要维持好秩序,而真正工作的则是大厅经理的手下——营业员,也就是我们刚刚提到的Visualizer。每天早上开工前(初始化阶段),大厅经理(DataSourceDisplay)把今天上班的营业员(Visualizer)都叫到一起,指着客户经理(EntityCollection)说,这位就是客户经理,所有的客户都是他来负责,你们认识一下,一定要和客户经理好好配合,服务好每一位客户啊。代码如下,这里又出来了一个Updater模块,实际上内部是Updater处理每一个客户的具体业务(将Entity转化为最终的Primitive),我们不妨认为Updater就是每一个营业员手中的电脑吧:
DataSourceDisplay.defaultVisualizersCallback = function(scene, entityCluster, dataSource) {
var entities = dataSource.entities;
return [new BillboardVisualizer(entityCluster, entities),
new GeometryVisualizer(BoxGeometryUpdater, scene, entities),
new GeometryVisualizer(CylinderGeometryUpdater, scene, entities),
new GeometryVisualizer(CorridorGeometryUpdater, scene, entities),
new GeometryVisualizer(EllipseGeometryUpdater, scene, entities),
new GeometryVisualizer(EllipsoidGeometryUpdater, scene, entities),
new GeometryVisualizer(PolygonGeometryUpdater, scene, entities),
new GeometryVisualizer(PolylineGeometryUpdater, scene, entities),
new GeometryVisualizer(PolylineVolumeGeometryUpdater, scene, entities),
new GeometryVisualizer(RectangleGeometryUpdater, scene, entities),
new GeometryVisualizer(WallGeometryUpdater, scene, entities),
new LabelVisualizer(entityCluster, entities),
new ModelVisualizer(scene, entities),
new PointVisualizer(entityCluster, entities),
new PathVisualizer(scene, entities)];
};
这里,客户经理(entities)在Visualizer的构造函数阶段耍了一个小花招,对每一个营业人员前面贴了一个标签,指明每一个营业员的业务范畴,跟每个营业员交代了一番,白纸黑字写的清清楚楚,到时候我把他们的材料审一边,然后让他们直接找你了。做管理也是要有两把刷子的,起码要知人善用,人尽其才。这时他走到其中一个营业员GeometryVisualizer身边仔细嘱咐了具体工作,我们来看看GeometryVisualizer的构造函数,看看人家的套路:
function GeometryVisualizer(type, scene, entityCollection) {
// Key 1
this._type = type; // Key 2
for (var i = 0; i < numberOfShadowModes; ++i) {
this._outlineBatches[i] = new StaticOutlineGeometryBatch(primitives, scene, i);
this._closedColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, true, i);
this._closedMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, true, i);
this._openColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, false, i);
this._openMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, false, i);
} // Key 3
entityCollection.collectionChanged.addEventListener(GeometryVisualizer.prototype._onCollectionChanged, this)
}
假设给他的安排(参数)如下:new GeometryVisualizer(RectangleGeometryUpdater, scene, entities)。 我们省去多余的客套话,直接看里面的三个要点:
- Key 1
你今天就专门负责Rectangle身份的客户 - Key 2
Rectangle属于几何对象,这类几何身份的客户,可能会有轮廓线,你得把他们的轮廓线信息放到_outlineBatches这一栏,它们有可能是闭合的,或者非闭合状态,也需要分别把他们的颜色和材质都筛选到对应栏 - Key 3
好了,我就简单的说道这里,一会Rectangle类的客户来了,我让它直接找你。浅层意思就是没事别找我,有事自己搞
细数下来,一共有六类客户,分别是:
- BillboardVisualizer
- GeometryVisualizer
- LabelVisualizer
- ModelVisualizer
- PointVisualizer
- PathVisualizer
其中,GeometryVisualizer是最常见的,就是我们最常见的屌丝,因为屌丝众多,屌丝和屌丝之间也是各有千秋,我们根据它们的外形(Geometry)分成了十类:
- BoxGeometry
- CylinderGeometry
- CorridorGeometry
- EllipseGeometry
- EllipsoidGeometry
- PolygonGeometry
- PolylineGeometry
- PolylineVolumeGeometry
- RectangleGeometry
- WallGeometryUpdater
EntityCollection分别就这六大类详细说明,有针对Geometry这类的十种情况额外强调一番。就到了营业时间。各就各位,准备接客。
EntityCollection.prototype.add
我们再次回到本文开头的代码片段:
entities.add({
rectangle : {
coordinates : Cesium.Rectangle.fromDegrees(-92.0, 20.0, -86.0, 27.0),
outline : true,
outlineColor : Cesium.Color.WHITE,
outlineWidth : 4,
stRotation : Cesium.Math.toRadians(45),
}
});
经常上面的介绍,我们比较清楚了DataSourceDisplay->DataSource->EntityCollection->Entity之间的层级关联,自然,专注的重点转到了业务流程。该客户类型为rectangle,有位置,有外边框,且宽度为4,颜色是白色,沿中心旋转45度。
EntityCollection.prototype.add = function(entity) {
if (!(entity instanceof Entity)) {
entity = new Entity(entity);
} var id = entity.id;
if (!this._removedEntities.remove(id)) {
this._addedEntities.set(id, entity);
} fireChangedEvent(this);
return entity;
};
EntityCollection看到这位客户,熟练的做了三个事情:1把它手里的材料整理好;2给它一张排队的小票;3通知对应的Visualizer接客。我们把这三个过程一一道来。
1.new Entity
function Entity(options) {
// 获取一个唯一id
// 可以指定,也可以系统自动创建一个
var id = options.id;
if (!defined(id)) {
id = createGuid();
} // 解析参数,把对应的属性赋给该entity
this.merge(options);
} Entity.prototype.merge = function(source) {
// 获取属性名,根据代码输入的参数,可知
// 调用的是Object.keys(source)
// 返回值是一个数组:0: "rectangle"
var sourcePropertyNames = defined(source._propertyNames) ? source._propertyNames : Object.keys(source);
var propertyNamesLength = sourcePropertyNames.length;
for (var i = 0; i < propertyNamesLength; i++) {
var name = sourcePropertyNames[i];
// 获取参数中"rectangle"对应的属性
var sourceProperty = source[name]; if (defined(sourceProperty)) {
// 将sourceProperty赋予该entity对应的rectangle属性
this[name] = sourceProperty;
}
}
};
这段代码看上去平淡无奇,很自然的通过用户输入的Object封装成一个新的Entity。实际上,这得益于Cesium强大的属性封装。在Cesium中,简单的属性可以通过defineProperties的形式,内部是采用Object.defineProperties的方式来提供set和get方法,但Entity中,不同的Geometry对应的属性并不完全相同,比如矩形是有左上角和右下角两个点就可以明确其position属性,而圆则需要圆心+半径,风格上也有一定差异;同时还有很多相同的属性,比如颜色等;另外一个特点是Entity对应的类型是可枚举的,所以Cesium在提供了createPropertyDescriptor类进行了封装,提高重用度。
defineProperties(Entity.prototype, {
rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics)
};
如上通过createPropertyTypeDescriptor实现了Entity中rectangle属性,正因为如此,才保证我们能够做到this[“rectangle”] = sourceProperty。另外,这里出现了一个新类RectangleGraphics,实际上是它针对rectangle的json参数进行的封装,此时Entity对应的rectangle属性,已经有初始化时的createPropertyDescriptor返回的function变成了该RectangleGraphics。此时的RectangleGraphics内部保存的是参数化的数据内容,并不参与几何数据处理的过程。稍后会提到。
2.EntityCollection._addedEntities
当Entity创建成功后,就好比客户经理检查完你的材料,确认无误后,给你一张小条,上面写着你的id,然后就是排队了:
EntityCollection.prototype.add = function(entity) {
if (!this._removedEntities.remove(id)) {
this._addedEntities.set(id, entity);
}
return entity;
};
这里想说的是,EntityCollection总共有三个更新队列:_addedEntities,_removedEntities,_changedEntities。由此可以得出,Entity的调度和Globe在设计上也是如出一辙,都是基于状态的。
3.fireChangedEvent
当把该Entity放入添加队列后,EntityCollection则通过ChangedEvent事件,通知所有绑定该事件的Visualizer,有新的Entity进来了。还记得Visualizer初始化时绑定的_onCollectionChanged事件:
GeometryVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed) {
var addedObjects = this._addedObjects;
var removedObjects = this._removedObjects;
var changedObjects = this._changedObjects; var i;
var id;
var entity; for (i = added.length - 1; i > -1; i--) {
entity = added[i];
id = entity.id;
if (removedObjects.remove(id)) {
changedObjects.set(id, entity);
} else {
addedObjects.set(id, entity);
}
}
};
针对EntityCollection的每一种队列,Visualizer分别提供了_addedObjects,_removedObjects,_changedObjects三个队列一一对接,负责的营业员一个不落的把客户从客户经理手中转交到自己的柜台前。这里,个人觉得Cesium还是有一个优化的空间,当然价值大不大就不清楚了。因为每一个Visualizer都会收到消息,说有一个新来的Entity,需要接待一下。这时,大家并不知道该Entity的类型,所以无法判断具体应该是哪一个Visualizer去接待。所以大家每个人都把该entity加到自己的添加队列了,这个在如上的代码可以看到。,最终是每一个Visualizer对应的Updater来解析该Entity,看看是不是自己的菜。其实我觉得,_onCollectionChanged事件中,可以很简单的通过Type来判断是否是当前Updater是否可解析该Entity,就好比看看菜单就能知道是否是自己的菜,没必要非要自己吃一口。
如上,我们在类的层次关系以及事件驱动两个维度介绍了AddEntity涉及到的具体内容。下篇介绍如何处理这些数据,最终通过DrawCommand使其可渲染。
Cesium原理篇:7最长的一帧之Entity(上)的更多相关文章
- Cesium原理篇:5最长的一帧之影像
如果把地球比做一个人,地形就相当于这个人的骨骼,而影像就相当于这个人的外表了.之前的几个系列,我们全面的介绍了Cesium的地形内容,详见: Cesium原理篇:1最长的一帧之渲染调度 Cesium原 ...
- Cesium原理篇:3最长的一帧之地形(2:高度图)
这一篇,接着上一篇,内容集中在高度图方式构建地球网格的细节方面. 此时,Globe对每一个切片(GlobeSurfaceTile)创建对应的TileTerrain类,用来维 ...
- Cesium原理篇:7最长的一帧之Entity(下)
上一篇,我们介绍了当我们添加一个Entity时,通过Graphics封装其对应参数,通过EntityCollection.Add方法,将EntityCollection的Entity传递到DataSo ...
- Cesium原理篇:3最长的一帧之地形(3:STK)
有了之前高度图的基础,再介绍STK的地形相对轻松一些.STK的地形是TIN三角网的,基于特征值,坦白说,相比STK而言,高度图属于淘汰技术,但高度图对数据的要求相对简单,而且支持实时构建网格,STK具 ...
- Cesium原理篇:1最长的一帧之渲染调度
原计划开始着手地形系列,但发现如果想要从逻辑上彻底了解地形相关的细节,那还是需要了解Cesium的数据调度过程,这样才能更好的理解,因此,打算先整体介绍一下Cesium的渲染过程,然后在过渡到其中的两 ...
- Cesium原理篇:3最长的一帧之地形(1)
前面我们从宏观上分析了Cesium的整体调度以及网格方面的内容,通过前两篇,读者应该可以比较清楚的明白一个Tile是怎么来的吧(如果还不明白全是我的错).接下来,在前两篇的基础上,我们着重讨论一下地形 ...
- Cesium原理篇:3最长的一帧之地形(4:重采样)
地形部分的原理介绍的差不多了,但之前还有一个刻意忽略的地方,就是地形的重采样.通俗的讲,如果当前Tile没有地形数据的话,则会从他父类的地形数据中取它所对应的四分之一的地形数据.打个比方 ...
- Cesium原理篇:2最长的一帧之网格划分
上一篇我们从宏观上介绍了Cesium的渲染过程,本章延续上一章的内容,详细介绍一下Cesium网格划分的一些细节,包括如下几个方面: 流程 Tile四叉树的构建 LOD 流程 首先,通过上篇的类关系描 ...
- Cesium原理篇:6 Render模块(6: Instance实例化)
最近研究Cesium的实例化,尽管该技术需要在WebGL2.0,也就是OpenGL ES3.0才支持.调试源码的时候眼前一亮,发现VAO和glDrawBuffers都不是WebGL1.0的标准函数,都 ...
随机推荐
- 在SQLSERVER里,怎么让别人只能输入一个字母的约束该怎么写?就是26个字母中的任意一个?
alter table 表名 add constraint ck_char check(自段名 like '[a-z]' or 自段名 like '[A-Z]')
- MVC自定义分页(附表跳转页Loading提示)
之前我发表了一篇MVC无刷新分页的文章,里面用的是MvcPager控件,但是那个受那个控件限制,传值只能用PagedList,各方面都受到了限制,自由度不够高,现在还是做MVC无刷新分页,但是想直接用 ...
- WPF整理-Mutex确保Application单例运行
有时我们不希望我们的WPF应用程序可以同时运行有多个实例,当我们试图运行第二个实例的时候,已经运行的实例也应该弹出来. 我们可以用Mutex来实现 打开App.xaml.cs,在App类中添加如下内容 ...
- 在windows上如何安装python web引擎jinja2
首先要把你的Python文件夹加到环境变量里头去.假设你的Python文件夹位于C:\Python34,那么你需要打开CMD并输入: SETX PATH "%path%;C:\Python3 ...
- MongoDB 2.6.2 发布
NoSQL数据库MongoDB推出了全新一代产品MongoDB 2.6.2,该版本全面强化核心服务器,提供全新的自动化工具与重要的企业功能,宣称是MongoDB问世5年来最大的一次版本发布,主要改善开 ...
- 夜深了,写了个JQuery的省市区三级级联效果
刚刚练手的JQuery,希望大神们指正 主要实现以下功能: 1.三级菜单级联加载数据 2.可以在不操作脚本的情况下,给元素加属性实现级联功能 3.自定义动态显示数据 咨询问题: 对于一般比较固定不变的 ...
- 移动Web触控事件总结
移动web风风火火几多年,让我这个在Pc端漂流的前端er不免心生仰慕,的确入行几多年,也该是时候进军移动web了.移动web中踩到的第一个坑就是事件问题,所以在吸取众大神的经验后,特作总结以示后来者. ...
- C语言 · 动态数组的使用
从键盘读入n个整数,使用动态数组存储所读入的整数,并计算它们的和与平均值分别输出.要求尽可能使用函数实现程序代码.平均值为小数的只保留其整数部分. 样例输入: 5 3 4 0 0 2样例输出:9 1样 ...
- Atitit 边缘检测原理attilax总结
Atitit 边缘检测原理attilax总结 1. 边缘检测的概念1 1.1. 边缘检测的用途1 2. 边缘检测方法分类1 3. 边缘检测的基本方法2 3.1. Roberts边缘检测算子2 3.2. ...
- 《Qt Quick 4小时入门》学习笔记2
http://edu.csdn.net/course/detail/1042/14805?auto_start=1 Qt Quick 4小时入门 第五章:Qt Quick基本界面元素介绍 1. ...