Cesium渲染调度
1. 引言
Cesium是一款三维地球和地图可视化开源JavaScript库,使用WebGL来进行硬件加速图形,使用时不需要任何插件支持,基于Apache2.0许可的开源程序,可以免费用于商业和非商业用途
Cesium官网:Cesium: The Platform for 3D Geospatial
Cesium GitHub站点:CesiumGS/cesium: An open-source JavaScript library for world-class 3D globes and maps (github.com)
API文档:Index - Cesium Documentation
通过阅读源码,理清代码逻辑,有助于扩展与开发,笔者主要参考了以下两个系列的文章
本文描述Cesium的渲染流程
2. 概述
Cesium渲染流程大致如图:
- endFrame()准备需要渲染的数据
- updateAndExecuteCommands()更新和调度渲染指令(图元对象含有渲染指令)
- Scene.render()进行绘制渲染
- CesiumWidget展示Scene.render()的内容并让其不断绘制(RenderLoop)
3. 渲染流程
Cesium的使用,往往是从创建Viewer开始的:
// ...
<div id="cesiumContainer"></div>
<script>
const viewer = new Cesium.Viewer('cesiumContainer')
</script>
// ...
new一个Viewer就可以创建一个地球,这其中经历了什么样的过程呢?
Viewer构造函数大致如下:
function Viewer(container, options) {
// ...
container = getElement(container);
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
// Cesium widget container
const cesiumWidgetContainer = document.createElement("div");
// Cesium widget
const cesiumWidget = new CesiumWidget(cesiumWidgetContainer);
// ...
}
Viewer主要由CesiumWidget构成,其构造函数大致如下:
function CesiumWidget(container, options) {
// ...
container = getElement(container);
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
// ...
const canvas = document.createElement("canvas");
const scene = new Scene({
canvas: canvas,
// ...
});
//Set the base imagery layer
let imageryProvider =
options.globe === false ? false : options.imageryProvider;
if (!defined(imageryProvider)) {
imageryProvider = createWorldImagery();
}
//Set the terrain provider if one is provided.
if (defined(options.terrainProvider) && options.globe !== false) {
scene.terrainProvider = options.terrainProvider;
}
// 设置循环渲染
this._useDefaultRenderLoop = undefined;
this.useDefaultRenderLoop = defaultValue(
options.useDefaultRenderLoop,
true
);
// ...
}
CesiumWidget中默认渲染函数useDefaultRenderLoop指向startRenderLoop:
useDefaultRenderLoop: {
// ...
startRenderLoop(this);
},
渲染函数startRenderLoop为:
function startRenderLoop(widget) {
// ...
let lastFrameTime = 0;
function render(frameTime) {
// ....
widget.render();
requestAnimationFrame(render);
// ...
}
requestAnimationFrame(render);
}
widget的render函数指向scene的render函数:
CesiumWidget.prototype.render = function () {
// ...
this._scene.render(currentTime);
};
scene的render函数:
function render(scene) {
// ...
if (defined(scene.globe)) {
scene.globe.beginFrame(frameState);
}
scene.updateEnvironment();
scene.updateAndExecuteCommands(passState, backgroundColor);
scene.resolveFramebuffers(passState);
if (defined(scene.globe)) {
scene.globe.endFrame(frameState);
}
// ...
}
updateAndExecuteCommands()负责管理,创建执行Commands的Tasks,自己并不负责Tasks内容的实现
scene.globe.endFrame()中,会对该帧所涉及的GlobeTile的下载,解析等进行处理
先看看scene.globe.endFrame()的数据处理:
scene.globe.endFrame()指向surface.endFrame()
Globe.prototype.endFrame = function (frameState) {
this._surface.endFrame(frameState);
};
surface是图元QuadtreePrimitive:
this._surface = new QuadtreePrimitive({
tileProvider: new GlobeSurfaceTileProvider({
terrainProvider: terrainProvider,
imageryLayers: imageryLayerCollection,
surfaceShaderSet: this._surfaceShaderSet,
}),
});
QuadtreePrimitive的endFrame()方法进行影像和高程的处理:
QuadtreePrimitive.prototype.endFrame = function (frameState) {
// ...
// Load/create resources for terrain and imagery. Prepare texture re-projections for the next frame.
processTileLoadQueue(this, frameState);
updateHeights(this, frameState);
updateTileLoadProgress(this, frameState);
};
再来看看updateAndExecuteCommands()方法:
Scene.prototype.updateAndExecuteCommands = function (passState, backgroundColor) {
// ...
executeCommandsInViewport(true, this, passState, backgroundColor);
// ...
};
updateAndExecuteCommands()方法主要进行更新执行各种Commands:
function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor) {
// ...
updateAndRenderPrimitives(scene);
executeCommands(scene, passState);
}
updateAndRenderPrimitives()进行更新:
this._primitives = new PrimitiveCollection();
function updateAndRenderPrimitives(scene) {
// ...
scene._groundPrimitives.update(frameState);
scene._primitives.update(frameState);
// ...
}
PrimitiveCollection.prototype.update = function (frameState) {
// ...
for (let i = 0; i < primitives.length; ++i) {
primitives[i].update(frameState);
}
};
进行纹理、外观和材质的更新:
Primitive.prototype.update = function (frameState) {
// ...
this._batchTable.update(frameState);
// ...
// Create or recreate render state and shader program if appearance/material changed
commandFunc(this, appearance,material,translucent,twoPasses,this._colorCommands,this._pickCommands,frameState);
};
BatchTable.prototype.update = function (frameState) {
// ...
updateTexture(this);
};
function updateTexture(batchTable) {
const dimensions = batchTable._textureDimensions;
batchTable._texture.copyFrom({
source: {
width: dimensions.x,
height: dimensions.y,
arrayBufferView: batchTable._batchValues,
},
});
}
executeCommands()调度执行命令
function executeCommands(scene, passState) {
// ...
// Draw terrain classification
executeCommand(commands[j], scene, context, passState);
// Draw 3D Tiles
executeCommand(commands[j], scene, context, passState)
// Draw classifications. Modifies 3D Tiles color.
executeCommand(commands[j], scene, context, passState);
// ...
}
function executeCommand(command, scene, context, passState, debugFramebuffer) {
// ...
if (command instanceof ClearCommand) {
command.execute(context, passState);
return;
}
// ...
command.execute(context, passState);
// ...
}
比如ClearCommand,它会执行clear命令:
ClearCommand.prototype.execute = function (context, passState) {
context.clear(this, passState);
};
最终命令变成GL指令:
Context.prototype.clear = function (clearCommand, passState) {
// ...
const c = clearCommand.color;
gl.clearColor(c.red, c.green, c.blue, c.alpha);
// ...
};