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實例化完畢,整個地球也就創建好了。
總的來說,整個渲染過程主要的三個實例化:
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!