Cesium源码跟读之CesiumWidget的实现

这是我参与更文挑战的第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模块。这部分代码完成了SceneGlobeSkyBoxSkyAtmosphere四个模块的实例化。

这里我们会发现一个问题,在使用CesiumWidget的时候,明明有像CameraImageryLayers之类的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
    );
}
复制代码

创建ellipsoidglobe并传递给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;
复制代码

最后对整个模块进行导出。

结构图

image-20210531155553870.png

最后

我们只是针对这个文件中的私有函数以及构造函数进行阅读和分析,并未对其引用的模块比如SceneGlobe等进行阅读。感兴趣的小伙伴可以针对其中一个引入的模块进行更深入的阅读。如果是想在Cesium上深入研究的话我认为有些功能和实现的源码大概通读一下,了解一下实现原理是有必要的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享