寻医问药
- 你想了解cesium的源码吗?
- 你是不是在庞大的源码中迷失过方向?
- cesium是如何一步步的把场景渲染出来的?
直奔主题
1、万物起源----时间
//Clock.js
Clock.prototype.tick = function () {
var currentSystemTime = getTimestamp();
var currentTime = JulianDate.clone(this._currentTime);
...//省略
this._currentTime = currentTime;
this._lastSystemTime = currentSystemTime;
this.onTick.raiseEvent(this);
return currentTime;
};
说明:通过代码可以看出当tick函数执行的时候会同步执行onTick中被订阅的动作。
2、渲染频率----帧率
//requestAnimationFrame.js
(function () {
// look for vendor prefixed function
if (!defined(implementation) && typeof window !== "undefined") {
var vendors = ["webkit", "moz", "ms", "o"];
var i = 0;
var len = vendors.length;
while (i < len && !defined(implementation)) {
implementation = window[vendors[i] + "RequestAnimationFrame"];
++i;
}
}
// build an implementation based on setTimeout
if (!defined(implementation)) {
var msPerFrame = 1000.0 / 60.0;
var lastFrameTime = 0;
implementation = function (callback) {
var currentTime = getTimestamp();
// schedule the callback to target 60fps, 16.7ms per frame,
// accounting for the time taken by the callback
var delay = Math.max(msPerFrame - (currentTime - lastFrameTime), 0);
lastFrameTime = currentTime + delay;
return setTimeout(function () {
callback(lastFrameTime);
}, delay);
};
}
})();
说明:通过代码可以看出cesium对各种浏览器的requestAnimationFrame函数做了兼容处理。
3、渲染----场景
Scene.prototype.render = function (time) {
this._preUpdate.raiseEvent(this, time);
var frameState = this._frameState;
frameState.newFrame = false;
...//省略
this._postUpdate.raiseEvent(this, time);
if (shouldRender) {
this._preRender.raiseEvent(this, time);
frameState.creditDisplay.beginFrame();
tryAndCatchError(this, render);
}
if (shouldRender) {
this._postRender.raiseEvent(this, time);
frameState.creditDisplay.endFrame();
}
};
说明:通过代码可以看出这个函数负责更新场景所有图元数据的状态,进而由数据自己根据状态决定是否渲染,顺便也看清楚了preRender、postUpdate这俩个重要的回调事件的触发机制。
调用过程
- 通过上面三个源代码片段已经可以看出整个调用流程了,下面我们一步步接开代码的执行逻辑
- 1、初始化场景
var viewer = new Viewer("cesiumContainer", {
imageryProvider: imageryProvider,
baseLayerPicker: hasBaseLayerPicker,
scene3DOnly: endUserOptions.scene3DOnly,
requestRenderMode: true,
});
- 2、new Viewer()做了什么?
function Viewer(container, options) {
...//省略
// Cesium widget
var cesiumWidget = new CesiumWidget();
...//省略
}
- 3、可以看出它核心是初始化了CesiumWidget,那么new CesiumWidget()又做了什么?
function CesiumWidget(container, options) {
...//省略
var scene = new Scene();
this._scene = scene;
this._screenSpaceEventHandler = new ScreenSpaceEventHandler(canvas);
this._useDefaultRenderLoop = undefined;
this.useDefaultRenderLoop = defaultValue(
options.useDefaultRenderLoop,
true
);
...//省略
}
- 4、可以看出它又new Scene()对象,然后把useDefaultRenderLoop属性设置为true。这个简单的一句话很重要,因为它是整个调用循环的开始一环,这个操作会触发下面一段代码
useDefaultRenderLoop: {
get: function () {
return this._useDefaultRenderLoop;
},
set: function (value) {
if (this._useDefaultRenderLoop !== value) {
this._useDefaultRenderLoop = value;
if (value && !this._renderLoopRunning) {
startRenderLoop(this);
}
}
},
},
}
- 5、startRenderLoop函数代码如下
function startRenderLoop(widget) {
widget._renderLoopRunning = true;
var lastFrameTime = 0;
function render(frameTime) {
...//省略
widget.resize();
widget.render();
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
- 6、从这段代码可以看出它调用了文章开头我们说的requestAnimationFrame函数,进而周而复始的执行widget.render()函数,其代码如下
CesiumWidget.prototype.render = function () {
if (this._canRender) {
this._scene.initializeFrame();
var currentTime = this._clock.tick();
this._scene.render(currentTime);
} else {
this._clock.tick();
}
};
- 7、从这段代码可以看出它调用了scene.render()函数进而把整个场景图元渲染出来,还调用了clock.tick()函数,从而执行onTick所有被订阅的函数。
复盘----总结梳理
- 先初始化Viewer对象
- 再初始化CesiumWidget对象
- 通过useDefaultRenderLoop属性触发requestAnimationFrame函数
- 通过requestAnimationFrame函数触发widget.render()函数
- 通过widget.render()函数触发scene.render和clock.tick()函数
- 完成后,周而复始4、5俩步
为了便于大家理解,我省略了本次渲染逻辑无关的相关代码,所以没有省略的代码都是非常重要的代码,建议大家对着这篇文章和源代码仔细认真阅读
更多----加入我们
- 这里有个地方需要说明,由于篇幅和时间有限,没有写出所有代码,这里主要列出来了主要实现思路
- 如果你还有不了解的地方
- 如果你还需要进行cesium交流
- 那一起学习探讨吧
- 你可以加入我们的基地,我们基地的地址是:450342630(QQ群号)