Cesium基于WebGL实现,所以渲染机制跟我们直接用WebGL的道理相同,但是内部功能强大,会更复杂些
目前不讨论Cesium的实现原理,先了解下Cesium的大体渲染流程的机制,有个初步认识
HelloWorld
亘古不变的HelloWorld,一句代码就可以绘制出来地球和很多小部件
var viewer = new Cesium.Viewer("cesiumContainer");
来一层一层看下地球是如何出来的
Viewer.js 相关代码
function Viewer(container, options) {
container = getElement(container);
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var viewerContainer = document.createElement("div");
viewerContainer.className = "cesium-viewer";
container.appendChild(viewerContainer);
// Cesium widget container
var cesiumWidgetContainer = document.createElement("div");
viewerContainer.appendChild(cesiumWidgetContainer);
// Bottom container
var bottomContainer = document.createElement("div");
viewerContainer.appendChild(bottomContainer);
// Cesium widget
var cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
imageryProvider: imageryProvider,
// ...
sceneMode: options.sceneMode,
});
// ...
// 初始化具体小部件、属性、事件等
}
Viewer()方法作为入口,构建必须的dom元素,初始化具体小部件、属性、事件等,很重要的一个就是实例化了CesiumWidget
,而CesiumWidget
内部构建了定时器,用来不断刷新并渲染页面
CesiumWidget.js 相关代码
// 定义定时器
function startRenderLoop(widget) {
var lastFrameTime = 0;
function render(frameTime) {
// ...
widget.resize();
widget.render();
requestAnimationFrame(render);
// ...
}
requestAnimationFrame(render);
}
function CesiumWidget(container, options) {
container = getElement(container);
//Configure the widget DOM elements
var element = document.createElement("div");
element.className = "cesium-widget";
container.appendChild(element);
var canvas = document.createElement("canvas");
canvas.addEventListener("mousedown", blurActiveElement);
canvas.addEventListener("pointerdown", blurActiveElement);
element.appendChild(canvas);
var scene = new Scene({
canvas: canvas,
// ...
});
this._scene = scene;
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
);
}
var skyBox = options.skyBox;
var skyAtmosphere = options.skyAtmosphere;
var imageryProvider = createWorldImagery();
scene.terrainProvider = options.terrainProvider;
// 给 useDefaultRenderLoop 属性赋值,触发了
this._useDefaultRenderLoop = undefined;
this.useDefaultRenderLoop = defaultValue(
options.useDefaultRenderLoop,
true
);
}
Object.defineProperties(CesiumWidget.prototype, {
// ...
// 通过set设置在赋值时,触发定时器
useDefaultRenderLoop: {
get: function () {
return this._useDefaultRenderLoop;
},
set: function (value) {
if (this._useDefaultRenderLoop !== value) {
this._useDefaultRenderLoop = value;
if (value && !this._renderLoopRunning) {
startRenderLoop(this);
}
}
},
}
})
// render方法
CesiumWidget.prototype.render = function () {
if (this._canRender) {
this._scene.initializeFrame();
var currentTime = this._clock.tick();
// 此时调用_scene的渲染方法
this._scene.render(currentTime);
} else {
this._clock.tick();
}
};
在this._scene.render(currentTime)
执行时,会调用scene
的渲染方法,来渲染场景中的各类要素
Scene.js 相关代码
function Scene(options) {
// 画布
var canvas = options.canvas;
// 上下文
var context = new Context(canvas, contextOptions);
this._id = createGuid();
this._jobScheduler = new JobScheduler();
// 帧的状态
this._frameState = new FrameState(
context,
new CreditDisplay(creditContainer, " • ", creditViewport),
this._jobScheduler
);
this._canvas = canvas;
this._context = context;
// ...
}
function render(scene) {
// ...
// globe地球渲染开始
scene.globe.beginFrame(frameState);
// 其它环境、覆盖物渲染等
scene.updateEnvironment();
// globe渲染执行,调用 Globe.render()方法
scene.updateAndExecuteCommands(passState, backgroundColor);
scene.resolveFramebuffers(passState);
executeOverlayCommands(scene, passState);
// globe地球渲染结束
scene.globe.endFrame(frameState);
}
Scene.prototype.render = function (time) {
tryAndCatchError(this, prePassesUpdate);
tryAndCatchError(this, updateMostDetailedRayPicks);
tryAndCatchError(this, updatePreloadPass);
tryAndCatchError(this, updatePreloadFlightPass);
// 调用render()
tryAndCatchError(this, render);
};
-
Scene
实例化时,定义好了画布, - 在
render
执行时,会调用updateAndExecuteCommands
, - 通过
executeCommands(scene, passState);
执行所有的Command
, - 而
Command
在执行时,会调用context._gl.drawElements()
方法,来调用webgl绘制图形 - 至此一帧就绘制完成,紧着这进入下一帧的绘制
另外此处可以看到调用了globe
的渲染方法,地球的绘制是通过地形和地图叠加到椭球体显示而成
Globe.js 相关代码
function Globe(ellipsoid) {
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
var terrainProvider = new EllipsoidTerrainProvider({
ellipsoid: ellipsoid,
});
var imageryLayerCollection = new ImageryLayerCollection();
this._ellipsoid = ellipsoid;
this._imageryLayerCollection = imageryLayerCollection;
this._surfaceShaderSet = new GlobeSurfaceShaderSet();
this._material = undefined;
this._surface = new QuadtreePrimitive({
tileProvider: new GlobeSurfaceTileProvider({
terrainProvider: terrainProvider,
imageryLayers: imageryLayerCollection,
surfaceShaderSet: this._surfaceShaderSet,
}),
});
// ...
}
Globe.prototype.beginFrame = function (frameState) {
var surface = this._surface;
var tileProvider = surface.tileProvider;
var terrainProvider = this.terrainProvider;
var hasWaterMask = this.showWaterEffect
// ...
surface.tileCacheSize = this.tileCacheSize;
tileProvider.terrainProvider = this.terrainProvider;
tileProvider.hasWaterMask = hasWaterMask;
surface.beginFrame(frameState);
// ...
};
Globe.prototype.render = function (frameState) {
this._surface.render(frameState);
};
地球表面包含着地形、切片、水面等,统一由tileProvider
处理,通过surface.beginFrame(frameState)
执行
由此,能看到各级关系如下: