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,   });   // ... } 

执行更新时调用ClearCommandexecute()方法

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();   // ... } 

执行更新时调用DrawCommandprimitives.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()方法使用DrawCommandClearCommand执行计算:

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)

[5] Cesium教程系列汇总 - fu*k - 博客园 (cnblogs.com)

发表评论

评论已关闭。

相关文章