Cesium 第一帧渲染指南

Cesium作为开源地图三维可视化的巨擘,在行业内有着广泛的应用。这里先不讨论内部实现原理,仅就渲染流程做个分析。

1 Viewer

下面一句话,就可以创建出一个默认的地球

var viewer = new Cesium.Viewer("cesiumContainer");

如下图所示,不仅有地球,还有右上角的工具栏,左下角的一个时钟,底部的一个文字标签和时间轴等小部件。这些都是如何呈现出来的呢,现在就进入到new Viewer() 内部看看。

cesium怎么学(第一帧渲染指南)(1)

function Viewer(container, options) { //... var that = this; Util.log("创建div容器viewerContainer,添加到map的div容器中"); var viewerContainer = document.createElement("div"); viewerContainer.className = "cesium-viewer"; container.appendChild(viewerContainer); // Cesium widget container Util.log("创建div容器cesiumWidgetContainer,添加到viewerContainer的div容器中") var cesiumWidgetContainer = document.createElement("div"); cesiumWidgetContainer.className = "cesium-viewer-cesiumWidgetContainer"; viewerContainer.appendChild(cesiumWidgetContainer); // Bottom container Util.log("创建bottomContainer的div容器添加到viewerContainer"); var bottomContainer = document.createElement("div"); bottomContainer.className = "cesium-viewer-bottom"; viewerContainer.appendChild(bottomContainer); //... //系统时钟 Util.log('创建Clock时间系统'); var clock; var clockViewModel; var destroyClockViewModel = false; if (defined(options.clockViewModel)) { clockViewModel = options.clockViewModel; clock = clockViewModel.clock; } else { clock = new Clock(); clockViewModel = new ClockViewModel(clock); destroyClockViewModel = true; } if (defined(options.shouldAnimate)) { clock.shouldAnimate = options.shouldAnimate; } // Cesium widget Util.log("\n CesiumWidget构造函数,初始化各种小部件,比如天空和skybox,图层的provider等\n 这里有个useDefaultRenderLoop布尔类型选项,默认为true,如果为false,则不会调用Viewer.render,就无法\n 循环渲染每一帧,只会渲染第一帧\n "); var cesiumWidget = new CesiumWidget(cesiumWidgetContainer, { imageryProvider: createBaseLayerPicker || defined(options.imageryProvider) ? false : undefined, clock: clock, skyBox: options.skyBox, skyAtmosphere: options.skyAtmosphere, sceneMode: options.sceneMode, mapProjection: options.mapProjection, globe: options.globe, orderIndependentTranslucency: options.orderIndependentTranslucency, contextOptions: options.contextOptions, useDefaultRenderLoop: options.useDefaultRenderLoop, targetframeRate: options.targetFrameRate, showRenderLoopErrors: options.showRenderLoopErrors, useBrowserRecommendedResolution: options.useBrowserRecommendedResolution, creditContainer: defined(options.creditContainer) ? options.creditContainer : bottomContainer, creditViewport: options.creditViewport, scene3DOnly: scene3DOnly, terrainExaggeration: options.terrainExaggeration, shadows: options.shadows, terrainShadows: options.terrainShadows, mapMode2D: options.mapMode2D, requestRenderMode: options.requestRenderMode, maximumRenderTimeChange: options.maximumRenderTimeChange, }); Util.log('工具小组件: '); Util.logObj(cesiumWidget); Util.log("创建dataSource集合dataSourceCollection"); var dataSourceCollection = options.dataSources; var destroyDataSourceCollection = false; if (!defined(dataSourceCollection)) { dataSourceCollection = new DataSourceCollection(); destroyDataSourceCollection = true; } var scene = cesiumWidget.scene; //... }

从代码中可以看到,首先创建了几个div容器,用来存放地球上的小部件。接着创建了一个Clock时钟。时钟创建之后,实例化了一个非常重要的变量 cesiumWidget,从代码中可以看到,cesiumWidget 的实例化中有很多参数,其中就包含上一步创建的 Clock 时钟。注意实例化 cesiumWidget 之后,会将其上面挂载的scene赋值给viewer里面的 scene。接着,我们进入到 cesiumWidget 中看实例化过程中都执行了什么。

2 CesiumWidget

function CesiumWidget(container, options) { //... Util.log("创建canvas"); var canvas = document.createElement("canvas"); try { //地球也是一个widget,创建一个场景 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; //... 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 ); } //... var skyBox = options.skyBox; this._useDefaultRenderLoop = undefined; //设置useDefaultRenderLoop Util.log("设置useDefaultRenderLoop,Cesium里面对useDefaultRenderLoop进行了劫持,在setter里面调用了渲染函数"); // debugger this.useDefaultRenderLoop = defaultValue( options.useDefaultRenderLoop, true ); //... } catch (error) { //... } }

可以看到,在cesiumWidget中创建了一个canvas,就是在这个canvas上面使用webgl绘制三维球的。接着又实例化了一个Scene,也就是创建了一个场景。注意在cesiumWidget中创建了地球相关的部件,比如天空盒,晨昏线,大气层等。还有一个至关重要的参数useDefaultRenderLoop,再往下翻,可以看到这个参数是做了数据劫持的:

useDefaultRenderLoop: { get: function () { return this._useDefaultRenderLoop; }, set: function (value) { if (this._useDefaultRenderLoop !== value) { this._useDefaultRenderLoop = value; if (value && !this._renderLoopRunning) { Util.log("在设置useDefaultRenderLoop的时候启动渲染"); startRenderLoop(this); } } }, },

用来判断是否对第一帧之后的每一帧进行渲染。如果设置且为true,则渲染,否则不渲染。

进入到实例化Scene的构造函数中来。

3 Scene

function Scene(options) { Util.log("创建Scene的初始选项: "); //根据GPU性能选择适合的渲染上下文渲染状态。可以是"high-performance", "low-power" 或者 "default"。默认为 "default"。 contextOptions.webgl.powerPreference = defaultValue( contextOptions.webgl.powerPreference, "high-performance" ); //... //>>includeEnd('debug'); var hasCreditContainer = defined(creditContainer); var context = new Context(canvas, contextOptions); this._jobScheduler = new JobScheduler(); this._frameState = new FrameState( context, new CreditDisplay(creditContainer, " • ", creditViewport), this._jobScheduler ); //... this._FrameState.scene3DOnly = defaultValue(options.scene3DOnly, false); this._removeCreditContainer = !hasCreditContainer; this._creditContainer = creditContainer; this._canvas = canvas; this._context = context; this._computeEngine = new ComputeEngine(context); this._globe = undefined; this._globeTranslucencyState = new GlobeTranslucencyState(); Util.log("创建PrimitiveCollection"); this._primitives = new PrimitiveCollection(); this._groundPrimitives = new PrimitiveCollection(); //... Util.log("创建事件,_preUpdate,_postUpdate,_renderError,_preRender,_postRender"); this._preUpdate = new Event(); this._postUpdate = new Event(); this._renderError = new Event(); this._preRender = new Event(); this._postRender = new Event(); //... updateFrameNumber(this, 0.0, JulianDate.now()); this.updateFrameState(); this.initializeFrame(); }

Scene中多是和webgl相关的。主要看最后三个函数,第一个函数updateFrameNumber更新帧数,第二个参数就是第几帧,很明显这里是第1帧,第二和第三个函数 this.updateFrameState(); this.initializeFrame();,就是对第一帧的渲染。

至此Scene实例化完成,回到cesiumWidget中去,紧接着在cesiumWidget中完成了地球的创建等。cesiumWidget实例化完成,回到Viewer中去,此时Viewer实例化完毕,整个地球也就创建好了。

总的来说,整个渲染过程主要的三个实例化:

,