Cesium渲染模块之Command
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); // ... };
6. 参考资料
[1] Cesium原理篇:6 Render模块(5: VAO&RenderState&Command) - fu*k - 博客园 (cnblogs.com)
[2] Cesium渲染模块之概述 - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)
[3] Cesium渲染调度 - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)
[4] CesiumJS 2022^ 源码解读 5 - 着色器相关的封装设计 - 岭南灯火 - 博客园 (cnblogs.com)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
2022-03-14 选择排序、冒泡排序、插入排序
2022-03-14 异或运算