Cesium 第一帧渲染指南
Cesium作为开源地图三维可视化的巨擘,在行业内有着广泛的应用。这里先不讨论内部实现原理,仅就渲染流程做个分析。
1 Viewer
下面一句话,就可以创建出一个默认的地球
var viewer = new Cesium.Viewer(“cesiumContainer”);
如下图所示,不仅有地球,还有右上角的工具栏,左下角的一个时钟,底部的一个文字标签和时间轴等小部件。这些都是如何呈现出来的呢,现在就进入到new Viewer() 内部看看。
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实例化完毕,整个地球也就创建好了。
总的来说,整个渲染过程主要的三个实例化:
● Viewer的实例化。
● 在Viewer的实例化中完成cesiumWidget的实例化。
● 在cesiumWidget的实例化中完成Scene的实例化。