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渲染模块的Command
2. Cesium中的Command
Cesium中的Command对象包含执行的指令参数和执行方法,比如最简单的ClearCommand:
function ClearCommand(options) { | |
// ... | |
this.color = options.color; | |
this.depth = options.depth; | |
this.stencil = options.stencil; | |
this.renderState = options.renderState; | |
this.framebuffer = options.framebuffer; | |
this.owner = options.owner; | |
this.pass = options.pass; | |
} | |
ClearCommand.prototype.execute = function (context, passState) { | |
context.clear(this, passState); | |
}; | |
ClearCommand包含颜色、深度、通道等指令参数和执行方法context.clear(this, passState)
Command对象主要有三类:
- ClearCommand
- DrawCommand
- ComputeCommand
正如其名,ClearCommand用于清除,DrawCommand用于绘制,ComputeCommand用于计算
3. ClearCommand
ClearCommand的封装很简单,如上述代码所示:
function ClearCommand(options) { | |
// ... | |
this.color = options.color; | |
this.depth = options.depth; | |
this.stencil = options.stencil; | |
this.renderState = options.renderState; | |
this.framebuffer = options.framebuffer; | |
this.owner = options.owner; | |
this.pass = options.pass; | |
} | |
ClearCommand.prototype.execute = function (context, passState) { | |
context.clear(this, passState); | |
}; |
context.clear()
会执行清除的WebGL指令:
Context.prototype.clear = function (clearCommand, passState) { | |
// ... | |
const c = clearCommand.color; | |
const d = clearCommand.depth; | |
const s = clearCommand.stencil; | |
gl.clearColor(c.red, c.green, c.blue, c.alpha); | |
gl.clearDepth(d); | |
gl.clearStencil(s); | |
bindFramebuffer(this, framebuffer); | |
gl.clear(bitmask); | |
}; |
ClearCommand在Scene中的调用:
初始化Scene时初始化ClearCommand
function Scene(options) { | |
// ... | |
this._clearColorCommand = new ClearCommand({ | |
color: new Color(), | |
stencil: 0, | |
owner: this, | |
}); | |
// ... | |
} |
执行更新时调用ClearCommand的execute
()方法
Scene.prototype.updateAndExecuteCommands = function (passState, backgroundColor) { | |
// ... | |
updateAndClearFramebuffers(this, passState, backgroundColor); | |
// ... | |
}; |
function updateAndClearFramebuffers(scene, passState, clearColor) { | |
// ... | |
// Clear the pass state framebuffer. | |
const clear = scene._clearColorCommand; | |
Color.clone(clearColor, clear.color); | |
clear.execute(context, passState); | |
// ... | |
} |
4. DrawCommand
DrawCommand是最常用的指令,它是绘制的主角
DrawCommand封装如下,几乎包含了绘制所需要的全部内容:
function DrawCommand(options) { | |
options = defaultValue(options, defaultValue.EMPTY_OBJECT); | |
this._boundingVolume = options.boundingVolume; | |
this._orientedBoundingBox = options.orientedBoundingBox; | |
this._modelMatrix = options.modelMatrix; | |
this._primitiveType = defaultValue( | |
options.primitiveType, | |
PrimitiveType.TRIANGLES | |
); | |
this._vertexArray = options.vertexArray; | |
this._count = options.count; | |
this._offset = defaultValue(options.offset, 0); | |
this._instanceCount = defaultValue(options.instanceCount, 0); | |
this._shaderProgram = options.shaderProgram; | |
this._uniformMap = options.uniformMap; | |
this._renderState = options.renderState; | |
this._framebuffer = options.framebuffer; | |
this._pass = options.pass; | |
this._owner = options.owner; | |
this._debugOverlappingFrustums = 0; | |
this._pickId = options.pickId; | |
// ... | |
} | |
DrawCommand.prototype.execute = function (context, passState) { | |
context.draw(this, passState); | |
}; |
context.draw()
执行WebGL的绘制指令:
Context.prototype.draw = function (drawCommand, passState, shaderProgram, uniformMap) { | |
// ... | |
beginDraw(this, framebuffer, passState, shaderProgram, renderState); | |
continueDraw(this, drawCommand, shaderProgram, uniformMap); | |
}; | |
function continueDraw(context, drawCommand, shaderProgram, uniformMap) { | |
// ... | |
va._bind(); | |
context._gl.drawArrays(primitiveType, offset, count); | |
// ... | |
va._unBind(); | |
} |
DrawCommand在Scene中的调用:
初始化Scene时初始化PrimitiveCollection
function Scene(options) { | |
// ... | |
this._primitives = new PrimitiveCollection(); | |
this._groundPrimitives = new PrimitiveCollection(); | |
// ... | |
} |
执行更新时调用DrawCommand的primitives.update(frameState)
()方法
Scene.prototype.updateAndExecuteCommands = function (passState, backgroundColor) { | |
// ... | |
executeCommandsInViewport(true, this, passState, backgroundColor); | |
// ... | |
}; | |
function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor) { | |
// ... | |
updateAndRenderPrimitives(scene); | |
// ... | |
} | |
function updateAndRenderPrimitives(scene) { | |
// ... | |
scene._groundPrimitives.update(frameState); | |
scene._primitives.update(frameState); | |
// ... | |
} |
再来看看primitives.update(frameState)
方法:
PrimitiveCollection.prototype.update = function (frameState) { | |
const primitives = this._primitives; | |
for (let i = 0; i < primitives.length; ++i) { | |
primitives[i].update(frameState); | |
} | |
}; | |
Primitive.prototype.update = function (frameState) { | |
// ... | |
const updateAndQueueCommandsFunc = updateAndQueueCommands | |
updateAndQueueCommandsFunc(...); | |
}; | |
function updateAndQueueCommands(...) { | |
// ... | |
const commandList = frameState.commandList; | |
const passes = frameState.passes; | |
if (passes.render || passes.pick) { | |
const colorLength = colorCommands.length; | |
for (let j = 0; j < colorLength; ++j) { | |
const colorCommand = colorCommands[j]; | |
// ... | |
commandList.push(colorCommand); | |
} | |
} | |
} |
primitives.update(frameState)
方法会将Command推入CommandList,然后在Scene中执行execute()
方法:
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) { | |
// ... | |
command.execute(context, passState); | |
// ... | |
} |
5. ComputeCommand
ComputeCommand需要配合ComputeEngine一起使用,可以将它认为是一个特殊的DrawCommand,通过渲染机制实现GPU的计算,通过Shader计算结果保存到纹理传出,实现在Web前端高效的处理大量的数值计算
ComputeCommand的构造函数如下:
function ComputeCommand(options) { | |
options = defaultValue(options, defaultValue.EMPTY_OBJECT); | |
this.vertexArray = options.vertexArray; | |
this.fragmentShaderSource = options.fragmentShaderSource; | |
this.shaderProgram = options.shaderProgram; | |
this.uniformMap = options.uniformMap; | |
this.outputTexture = options.outputTexture; | |
this.preExecute = options.preExecute; | |
this.postExecute = options.postExecute; | |
this.canceled = options.canceled; | |
this.persists = defaultValue(options.persists, false); | |
this.pass = Pass.COMPUTE; | |
this.owner = options.owner; | |
} | |
ComputeCommand.prototype.execute = function (computeEngine) { | |
computeEngine.execute(this); | |
}; |
computeEngine.execute()
方法使用DrawCommand和ClearCommand执行计算:
ComputeEngine.prototype.execute = function (computeCommand) { | |
// ... | |
computeCommand.preExecute(computeCommand); | |
const outputTexture = computeCommand.outputTexture; | |
const framebuffer = createFramebuffer(context, outputTexture); | |
// ... | |
clearCommand.execute(context); | |
drawCommand.framebuffer = framebuffer; | |
drawCommand.execute(context); | |
framebuffer.destroy(); | |
computeCommand.postExecute(outputTexture); | |
}; |
ImageryLayer.js
中重投影就使用了ComputeCommand:
ImageryLayer.prototype._reprojectTexture = function (frameState, imagery, needGeographicProjection) { | |
// ... | |
const computeCommand = new ComputeCommand({ | |
persists: true, | |
owner: this, | |
// Update render resources right before execution instead of now. | |
// This allows different ImageryLayers to share the same vao and buffers. | |
preExecute: function (command) { | |
reprojectToGeographic(command, context, texture, imagery.rectangle); | |
}, | |
postExecute: function (outputTexture) { | |
imagery.texture = outputTexture; | |
that._finalizeReprojectTexture(context, outputTexture); | |
imagery.state = ImageryState.READY; | |
imagery.releaseReference(); | |
}, | |
canceled: function () { | |
imagery.state = ImageryState.TEXTURE_LOADED; | |
imagery.releaseReference(); | |
}, | |
}); | |
this._reprojectComputeCommands.push(computeCommand); | |
// ... | |
}; |