这是我参与更文挑战的第2天,活动详情查看: 更文挑战。
前言
没错,我也来读源码了,因为之前的文章中简单提到了一下Cesium的渲染机制,所以我索性一咬牙将这块的源码通读一遍,自己也能更深入的了解Cesium,这篇文章也就作为我的一个记录。准备工作只有一步:从GitHub上拉取一份Cesium最新的代码,找到Source\Widgets\CesiumWidget\CesiumWidget.js
文件。
文章中代码仅为涉及到讲解部分的代码,未全部放出
用法
CesiumWidget的用法和Viewer的用法类似,都是通过一个id来生成容器。
const widget = new Cesium.CesiumWidget('id');
const viewer = new Cesium.Viewer('id');
复制代码
两者的区别是,只要实例化了Viewer则必定会实例化一个CesiumWidget。Viewer除了可视区域外还囊括了各种控件,比如时间轴、时间拨盘、搜索框等。
Cesium中使用WebGL进行绘图的是实例化的Scene模块。CesiumWidget更像一个桥梁,把构造时传递的DOM元素(或ID选择器)再内嵌了一个canvas元素,再将此canvas元素传递给Scene让其绘图。
内部方法
- [24行]
getDefaultSkyBoxUrl
:获取默认天空盒资源地址 - [30行]
startRenderLoop
:开启渲染循环,这也是我们上一篇中提到的Cesium的渲染机制。利用requestAnimationFrame
来不停的刷新渲染页面。 - [71行]
configurePixelRatio
:配置像素比。 - [86行]
configureCanvasSize
:配置画布大小。 - [105行]
configureCameraFrustum
:配置相机视锥。
构造
DOM构造
function CesiumWidget(container, options) {
if (!defined(container)) {
throw new DeveloperError("container is required.");
}
container = getElement(container);
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//Configure the widget DOM elements
var element = document.createElement("div");
element.className = "cesium-widget";
container.appendChild(element);
var canvas = document.createElement("canvas");
// ……
element.appendChild(canvas);
}
复制代码
这部分代码完成了Viewer实例的DOM元素判断、本级DOM元素创建和下级DOM(canvas
)的创建,以及传入的options
是否为空。
如此DOM的层级便生成完毕了,下方代码(227~263行)主要是像商标这种部分以及分辨率等设置、将CesiumWidget的私有变量赋值并调用configureCanvasSize
来修改canvas
的大小。
场景构造
从265行开始我们会看到一个巨大的try/catch
模块。这部分代码完成了Scene
、Globe
、SkyBox
、SkyAtmosphere
四个模块的实例化。
这里我们会发现一个问题,在使用CesiumWidget
的时候,明明有像Camera
、ImageryLayers
之类的API,是从哪来的呢。这里先卖个关子,我们先专注于Scene
。
var scene = new Scene({
canvas: canvas,
contextOptions: options.contextOptions,
creditContainer: innerCreditContainer,
creditViewport: creditViewport,
mapProjection: options.mapProjection,
orderIndependentTranslucency: options.orderIndependentTranslucency,
scene3DOnly: defaultValue(options.scene3DOnly, false),
terrainExaggeration: options.terrainExaggeration,
shadows: options.shadows,
mapMode2D: options.mapMode2D,
requestRenderMode: options.requestRenderMode,
maximumRenderTimeChange: options.maximumRenderTimeChange,
});
this._scene = scene;
复制代码
实例化Scene对象,传递构造的参数(绝大部分来自于options)。
scene.camera.constrainedAxis = Cartesian3.UNIT_Z;
configurePixelRatio(this);
configureCameraFrustum(this);
复制代码
指定camera
的约束轴为Z轴,调用函数来配置像素比、相机视锥体。
var ellipsoid = defaultValue(
scene.mapProjection.ellipsoid,
Ellipsoid.WGS84
);
var globe = options.globe;
if (!defined(globe)) {
globe = new Globe(ellipsoid);
}
if (globe !== false) {
scene.globe = globe;
scene.globe.shadows = defaultValue(
options.terrainShadows,
ShadowMode.RECEIVE_ONLY
);
}
复制代码
创建ellipsoid
和globe
并传递给scene
。
var skyBox = options.skyBox;
if (!defined(skyBox)) {
skyBox = new SkyBox({
sources: {
positiveX: getDefaultSkyBoxUrl("px"),
negativeX: getDefaultSkyBoxUrl("mx"),
positiveY: getDefaultSkyBoxUrl("py"),
negativeY: getDefaultSkyBoxUrl("my"),
positiveZ: getDefaultSkyBoxUrl("pz"),
negativeZ: getDefaultSkyBoxUrl("mz"),
},
});
}
if (skyBox !== false) {
scene.skyBox = skyBox;
scene.sun = new Sun();
scene.moon = new Moon();
}
// Blue sky, and the glow around the Earth's limb.
var skyAtmosphere = options.skyAtmosphere;
if (!defined(skyAtmosphere)) {
skyAtmosphere = new SkyAtmosphere(ellipsoid);
}
if (skyAtmosphere !== false) {
scene.skyAtmosphere = skyAtmosphere;
}
复制代码
创建天空盒、大气环境等环境因素。
//Set the base imagery layer
var imageryProvider =
options.globe === false ? false : options.imageryProvider;
if (!defined(imageryProvider)) {
imageryProvider = createWorldImagery();
}
if (imageryProvider !== false) {
scene.imageryLayers.addImageryProvider(imageryProvider);
}
//Set the terrain provider if one is provided.
if (defined(options.terrainProvider) && options.globe !== false) {
scene.terrainProvider = options.terrainProvider;
}
复制代码
设置影像数据源,如果没有配置,则调用createWorldImagery
模块创建世界影像并传递给scene
,如果配置中有地形数据源,则将其传递给scene
。
this._screenSpaceEventHandler = new ScreenSpaceEventHandler(canvas);
复制代码
创建事件处理器,用于下方暴露在原型中。
if (defined(options.sceneMode)) { if (options.sceneMode === SceneMode.SCENE2D) { this._scene.morphTo2D(0); } if (options.sceneMode === SceneMode.COLUMBUS_VIEW) { this._scene.morphToColumbusView(0); }}
复制代码
判断scene
的视图模式是二维、三维还是2.5的。
var that = this;this._onRenderError = function (scene, error) { that._useDefaultRenderLoop = false; that._renderLoopRunning = false; if (that._showRenderLoopErrors) { var title = "An error occurred while rendering. Rendering has stopped."; that.showErrorPanel(title, undefined, error); }};scene.renderError.addEventListener(this._onRenderError);
复制代码
给scene
绑定了渲染错误事件的处理函数。
this._useDefaultRenderLoop = undefined;this.useDefaultRenderLoop = defaultValue( options.useDefaultRenderLoop, true);this._targetFrameRate = undefined;this.targetFrameRate = options.targetFrameRate;
复制代码
确认是否使用默认循环机制,如果配置为false则需要手动调用render()
方法,否则使用目标帧速率进行渲染。
原型定义
我们在上边提过,CesiumWidget
那么多的API都是从哪来的,代码读到这里我们就会发现,从390行向下,都是利用Object.defineProperties()
在CesiumWidget的原型上添加属性以及方法。
属性定义
Object.defineProperties(CesiumWidget.prototype, { container: { get: function () { return this._container; }, }, canvas: { get: function () { return this._canvas; }, }, // ……}
复制代码
方法定义
- [640行]
CesiumWidget.prototype.showErrorPanel
:向用户显示错误面板,其中包含标题和较长的错误消息,可以使用’确定’按钮将其关闭。该面板自动显示发生渲染循环错误时,如果showRenderLoopErrors不为false,则当小部件已构建。 - [740行]
CesiumWidget.prototype.isDestroyed
:返回CesiumWidget
是否被销毁。 - [748行]
CesiumWidget.prototype.destroy
:销毁视图。 - [763行]
CesiumWidget.prototype.resize
:更新画布大小、相机纵横比和视口大小。这个函数默认会自动调用,除非useDefaultRenderLoop
设置为false
。 - [785行]
CesiumWidget.prototype.render
:渲染场景,同样的它也会自动调用,除非useDefaultRenderLoop
设置为false
。
导出
export default CesiumWidget;
复制代码
最后对整个模块进行导出。
结构图
最后
我们只是针对这个文件中的私有函数以及构造函数进行阅读和分析,并未对其引用的模块比如Scene
、Globe
等进行阅读。感兴趣的小伙伴可以针对其中一个引入的模块进行更深入的阅读。如果是想在Cesium上深入研究的话我认为有些功能和实现的源码大概通读一下,了解一下实现原理是有必要的。