Cesium之DrawCommand与绘制三角形

Posted 当时明月在曾照彩云归

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cesium之DrawCommand与绘制三角形相关的知识,希望对你有一定的参考价值。

本文描述Cesium中的DrawCommand并绘制一个自定义的三角形

1. 引言

Cesium中的Command对象包含执行的指令参数和执行方法,Command对象主要有三类:

  • ClearCommand
  • DrawCommand
  • ComputeCommand

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);
;

DrawCommand包含了绘制所需的VAO、ShaderProgram等参数以及execute()方法

DrawCommand是Cesium对WebGL 底层的封装 ,可以说操作DrawCommand就是在操作底层WebGL

本文描述Cesium中的DrawCommand并绘制一个自定义的三角形

2. Scene中的DrawCommand

Scene是Cesium中是一个很上层的概念,描述一个场景情况,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);
  // ...

综上,在Scene中会调用primitives[i].update(frameState)实现primitive绘制与更新

3. 绘制一个三角形

综上,要绘制一个自定义的三角形,需要构建一个primitive添加到Scene中,并且需要设置primitiveupdate(frameState)方法,实现自定义三角形的绘制与更新

primitiveupdate(frameState)方法,实质上就是构建DrawCommand,并将其推入commandList,然后执行execute()方法实现绘制与更新

总结一下步骤就是:

  • 构建DrawCommand
  • 构建primitive并设置update(frameState)方法
  • primitive添加到Scene

3.1 构建DrawCommand

从引言处的DrawCommand构造函数可以得出,构建一个DrawCommand需要:

  • modelMatrix
  • vertexArray
  • shaderProgram
  • uniformMap
  • renderState
  • pass
  • ......

3.1.1 vertexArray

参考源码VertexArray.js中创建一个VAO和VBO的例子:

// Example 1. Create a vertex array with vertices made up of three floating point
// values, e.g., a position, from a single vertex buffer.  No index buffer is used.
const positionBuffer = Buffer.createVertexBuffer(
    context : context,
    sizeInBytes : 12,
    usage : BufferUsage.STATIC_DRAW
);
const attributes = [
    
        index                  : 0,
        enabled                : true,
        vertexBuffer           : positionBuffer,
        componentsPerAttribute : 3,
        componentDatatype      : ComponentDatatype.FLOAT,
        normalize              : false,
        offsetInBytes          : 0,
        strideInBytes          : 0 // tightly packed
        instanceDivisor        : 0 // not instanced
    
];
const va = new VertexArray(
    context : context,
    attributes : attributes
);

以及Buffer.js中创建一个指定顶点的例子:

// Example 2. Create a dynamic vertex buffer from three floating-point values.
// The data copied to the vertex buffer is considered raw bytes until it is
// interpreted as vertices using a vertex array.
const positionBuffer = buffer.createVertexBuffer(
    context : context,
    typedArray : new Float32Array([0, 0, 0]),
    usage : BufferUsage.STATIC_DRAW
);

综上,创建一个自定义三角形的vertexArray代码如下:

const positionBuffer = buffer.createVertexBuffer(
    context : context,
    typedArray : new Float32Array([0, 0, 0]),
    usage : BufferUsage.STATIC_DRAW
);
const attributes = [
    
        index                  : 0,
        enabled                : true,
        vertexBuffer           : positionBuffer,
        componentsPerAttribute : 3,
        componentDatatype      : ComponentDatatype.FLOAT,
        normalize              : false,
        offsetInBytes          : 0,
        strideInBytes          : 0, // tightly packed
        instanceDivisor        : 0  // not instanced
    
];
const va = new VertexArray(
    context : context,
    attributes : attributes
);

其中,contextframeState.context,而frameStateScene中保存的帧状态

3.1.2 shaderProgram

ShaderProgram包含GLSL、Shader、Shader Program等一系列参数

更为具体的ShaderProgram描述可以参考:Cesium渲染模块之Shader - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)

在Cesium源码中PolylineCollection.js中创建一个ShaderProgram的例子:

this.shaderProgram = ShaderProgram.fromCache(
    context: context,
    vertexShaderSource: vs,
    fragmentShaderSource: fs,
    attributeLocations: attributeLocations,
);

其中,vertexShaderSourcefragmentShaderSource支持直接编写GLSL代码,因为在ShaderCache.js中做了处理:

if (typeof vertexShaderSource === "string") 
    vertexShaderSource = new ShaderSource(
        sources: [vertexShaderSource],
    );


if (typeof fragmentShaderSource === "string") 
    fragmentShaderSource = new ShaderSource(
        sources: [fragmentShaderSource],
    );

综上,创建一个自定义三角形的shaderProgram代码如下:

const vertexShaderSource = `
    attribute vec3 position;
    void main() 
      gl_Position = czm_projection * czm_view * czm_model * vec4(position, 1.0);
    
`
const fragmentShaderSource = `
    void main()
      gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    
`
const attributeLocations = 
    "position": 0,


const shaderProgram = ShaderProgram.fromCache(
    context: context,
    vertexShaderSource: vs,
    fragmentShaderSource: fs,
    attributeLocations: attributeLocations,
);

3.1.3 modelMatrix

在Cesium源码DrawCommand.js中描述modelMatrix:The transformation from the geometry in model space to world space

Primitive.js中,可以看到DrawCommandmodelMatrix通常来自于PrimitivemodelMatrix

// ...
colorCommand.modelMatrix = modelMatrix;

3.2 构建Primitive

此处构建Primitive倒也不复杂,因为并不设置什么参数,只需指定modelMatrixupdate(frameState)方法即可

3.2.1 modelMatrix

在Cesium源码DrawCommand.js中描述modelMatrix

The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates. When this is the identity matrix, the primitive is drawn in world coordinates, i.e., Earth\'s WGS84 coordinates. Local reference frames can be used by providing a different transformation matrix, like that returned by Transforms.eastNorthUpToFixedFrame

并且给出示例:

const origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
p.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);

综上,设置一个自定义的modelMatrix

const modelCenter = Cesium.Cartesian3.fromDegrees(121.474509, 31.233368, 0)
const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(modelCenter)
  • (121.474509, 31.233368)在上海附近

3.2.2 update方法

在Cesium源码Primitive.js中,可以看到Primitive.update()方法就是在创建command并推入commandList

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);
    
  

所以,创建自定义的update()函数也是要完成创建command并推入commandList

function update(frameState) 
    const command = createCommand(frameState, this._modelMatrix)
    frameState.commandList.push(command)

3.2.3 完整代码

综上,绘制一个自定义的三角形的Primitive代码如下:

class CustomPrimitive extends Cesium.Primitive 
    constructor(options) 
        super(options)
        const modelCenter = Cesium.Cartesian3.fromDegrees(121.474509, 31.233368, 0)
        const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(modelCenter)
        this._modelMatrix = modelMatrix
    

    createCommand = (frameState, modelMatrix) => 
        const context = frameState.context
        const positionBuffer = Cesium.Buffer.createVertexBuffer(
            context: context,
            typedArray: new Float32Array([
                100000, 500000, 50000,
                -200000, -100000, 50000,
                500000, -300000, 50000,
            ]),
            usage: Cesium.BufferUsage.STATIC_DRAW
        );
        const attributes = [
            
                index: 0,
                enabled: true,
                vertexBuffer: positionBuffer,
                componentsPerAttribute: 3,
                componentDatatype: Cesium.ComponentDatatype.FLOAT,
                normalize: false,
                offsetInBytes: 0,
                strideInBytes: 0,  // tightly packed
                instanceDivisor: 0 // not instanced
            
        ];
        const vertexArray = new Cesium.VertexArray(
            context: context,
            attributes: attributes
        );

        const vertexShaderSource = `
          attribute vec3 position;
          void main() 
            gl_Position = czm_projection * czm_view * czm_model * vec4(position, 1.0);
          
      `
        const fragmentShaderSource = `
          void main()
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
          
      `
        const attributeLocations = 
            "position": 0,
        

        const shaderProgram = Cesium.ShaderProgram.fromCache(
            context: context,
            vertexShaderSource: vertexShaderSource,
            fragmentShaderSource: fragmentShaderSource,
            attributeLocations: attributeLocations,
        );

        const renderState = Cesium.RenderState.fromCache(
            depthTest: 
                enabled: true
            
        );

        return new Cesium.DrawCommand(
            modelMatrix: modelMatrix,
            vertexArray: vertexArray,
            shaderProgram: shaderProgram,
            renderState: renderState,
            pass: Cesium.Pass.OPAQUE,
        )
    

    update = (frameState) => 
        const command = this.createCommand(frameState, this._modelMatrix)
        frameState.commandList.push(command)
    


3.3 添加到Scene中

创建一个Primitive,然后添加到Scene中:

const viewer = new Cesium.Viewer(\'cesiumContainer\');
const customPrimitive = new CustomPrimitive()
viewer.scene.primitives.add(customPrimitive)

完整代码:

<body>
  <div id="cesiumContainer"></div>
  <script>
    Cesium.Ion.defaultAccessToken = \'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJlMTk4ZTYyNy00MjkxLTRmZWYtOTg1MS0wOThjM2YzMzIzYzEiLCJpZCI6NzEyMSwic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU0ODMxNzI5OX0.rKV8Ldl_bgR3lVvNsbHhTX62j8JH8ADCIWAwk7tXpr8\';

    class CustomPrimitive extends Cesium.Primitive 
      constructor(options) 
        super(options)
        const modelCenter = Cesium.Cartesian3.fromDegrees(121.474509, 31.233368, 0)
        const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(modelCenter)
        this._modelMatrix = modelMatrix
      

      createCommand = (frameState, modelMatrix) => 
        const context = frameState.context
        const positionBuffer = Cesium.Buffer.createVertexBuffer(
          context: context,
          typedArray: new Float32Array([
            100000, 500000, 50000,
            -200000, -100000, 50000,
            500000, -300000, 50000,
          ]),
          usage: Cesium.BufferUsage.STATIC_DRAW
        );
        const attributes = [
          
            index: 0,
            enabled: true,
            vertexBuffer: positionBuffer,
            componentsPerAttribute: 3,
            componentDatatype: Cesium.ComponentDatatype.FLOAT,
            normalize: false,
            offsetInBytes: 0,
            strideInBytes: 0,  // tightly packed
            instanceDivisor: 0 // not instanced
          
        ];
        const vertexArray = new Cesium.VertexArray(
          context: context,
          attributes: attributes
        );

        const vertexShaderSource = `
          attribute vec3 position;
          void main() 
            gl_Position = czm_projection * czm_view * czm_model * vec4(position, 1.0);
          
      `
        const fragmentShaderSource = `
          void main()
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
          
      `
        const attributeLocations = 
          "position": 0,
        

        const shaderProgram = Cesium.ShaderProgram.fromCache(
          context: context,
          vertexShaderSource: vertexShaderSource,
          fragmentShaderSource: fragmentShaderSource,
          attributeLocations: attributeLocations,
        );

        const renderState = Cesium.RenderState.fromCache(
          depthTest: 
            enabled: true
          
        );

        return new Cesium.DrawCommand(
          modelMatrix: modelMatrix,
          vertexArray: vertexArray,
          shaderProgram: shaderProgram,
          renderState: renderState,
          pass: Cesium.Pass.OPAQUE,
        )
      

      update = (frameState) => 
        const command = this.createCommand(frameState, this._modelMatrix)
        frameState.commandList.push(command)
      
    

    const viewer = new Cesium.Viewer(\'cesiumContainer\');
    const customPrimitive = new CustomPrimitive()
    viewer.scene.primitives.add(customPrimitive)
  </script>
</body>

实现的结果:

3. 参考资料

[1] Cesium DrawCommand [1] 不谈地球 画个三角形 - 岭南灯火 - 博客园 (cnblogs.com)

[2] Cesium渲染模块之Command - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)

[3] Cesium渲染模块之Shader - 当时明月在曾照彩云归 - 博客园 (cnblogs.com)

cesium源码研究之uniformMap的自动更新机制

在利用drawcommand绘制图形的时候,会通过uniformMap传递变量到着色器,如果用到异步加载图片生成纹理再传递给uniformMap,这个时候需要需要注意return的方式。应先判断需要传值的纹理是否为null或者undefined,然后根据判断结果是return 所传的纹理,还是context的默认纹理。即使此时Resource异步加载图片生成纹理没有完成,此时应传入context的默认纹理,等到Resource加载完毕,传入的context的默认纹理会被Resource加载图片生成的纹理所覆盖。
1、生成纹理示例

  //图像纹理读取与创建
    let texture = undefined;
    let imageUri = '../static/images/bonsai.raw.png';
    let vtxfTexture = undefined;
     Cesium.Resource.createIfNeeded(imageUri).fetchImage().then(function (image) 
        console.log('image loaded!');
        console.log(image);
        
        //debugger
        vtxfTexture = new Cesium.Texture(
            context: context,
            width: image.width,
            height: image.height,
            source: 
                arrayBufferView: image.bufferView
            ,
            hasMipmap: false,
            sampler: sampler,
            pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE
        );
        vtxfTexture.type = 'sampler2D';  
        //debugger
        that._cubeTextures = vtxfTexture   
    );

2、uniformMap传值示例

    let uniformMapFront = 
        tex: function () 
            return that._framebuffer._colorTextures[0]      
        ,
        cubeTex: function () 
            if (Cesium.defined(that._cubeTextures)) 
                return that._cubeTextures;
             else 
                return context.defaultTexture;
            
        
    

以上是关于Cesium之DrawCommand与绘制三角形的主要内容,如果未能解决你的问题,请参考以下文章

cesium源码研究之uniformMap的自动更新机制

Cesium的渲染都是通过DrawCommand来完成

Cesium Primitive API 实践:绘制一个三角形

cesuim 绘图贴模型怎么获取范围之内的三角网

Cesium对3dtile单个feature进行特效处理

Cesium对3dtile单个feature进行特效处理