current position:Home>Cesium drawcommand [1] draw a triangle without talking about the earth

Cesium drawcommand [1] draw a triangle without talking about the earth

2022-04-29 13:12:41Songs in the four seasons

0. Preface

Primitive API Is an open API At the bottom of , It is oriented to high-performance scenarios 、 Customizable material shaders (Appearance API + FabricMaterial Specification)、 Static 3D object .

For all that ,Primitive API Still encapsulates a large number of Geometry classes 、 Material class 、WebWorker, And currently open custom shaders API Only the new architecture of 3D model class , It hasn't been dropped to Primitive API.

If API The burden doesn't want to be so heavy , I hope I can use my own model format ( Must be triangular ), So private DrawCommand + VertexArray The interface is very suitable , Its style is the closest to CesiumJS WebGL Bottom class API 了 .

DrawCommand, yes Cesium encapsulation WebGL An excellent design of , It takes the drawing data (VertexArray) And drawing behavior (ShaderProgram) As an object , When the time is right , That is to say Scene perform executeCommand Function time , All instruction objects on the frame state object will use WebGL Function execution , Whatever you want bind what , The usage in drawing is consistent , The upper application interface only needs to generate instruction objects .

0.1. In the source DrawCommand

For example, in Primitive.js Module createCommands function , It is responsible for Primitive Object's parameterized data or WebWorker The calculated data is merged to generate DrawCommand The place of :

 function createCommands(/*  Parameter omitted  */) {
   // ...
   const length = colorCommands.length;
   let vaIndex = 0;
   for (let i = 0; i < length; ++i) {
     let colorCommand;
 ​
     // ...
 ​
     colorCommand = colorCommands[i];
     if (!defined(colorCommand)) {
       colorCommand = colorCommands[i] = new DrawCommand({
         owner: primitive, //  Enter the reference , namely  Primitive  object 
         primitiveType: primitive._primitiveType,
       });
     }
     colorCommand.vertexArray = primitive._va[vaIndex]; // VertexArray
     colorCommand.renderState = primitive._frontFaceRS; //  Render states 
     colorCommand.shaderProgram = primitive._sp; // ShaderProgram
     colorCommand.uniformMap = uniforms; //  Unity value 
     colorCommand.pass = pass; //  The channel order of the instruction 
   }
   // ...
 }
 Copy code 

1. establish

1.1. Constituent elements - VertexArray

Cesium hold WebGL The vertex buffer and index buffer are wrapped into Buffer, And then for convenience , Bind these vertex related buffers to an object , be called VertexArray, It will be enabled internally WebGL Of VAO function .

The fastest way to create VertexArray The way to , Is to call its static method VertexArray.fromGeometry(), But it takes Geometry API To help. .

I want to use it directly here Buffer To illustrate , Then you have to create Buffer

 const positionBuffer = Buffer.createVertexBuffer({
   context: context,
   sizeInBytes: 12,
   usage: BufferUsage.STATIC_DRAW,
   typedArray: new Float32Array([/* ... */])
 })
 const attributes = [
   {
     index: 0,
     enabled: true,
     vertexBuffer: positionBuffer,
     componentsPerAttribute: 3,
     componentDatatype: ComponentDatatype.FLOAT,
     normalize: false,
     offsetInBytes: 0,
     strideInBytes: 0, //  Close together , No,  byteStride
     instanceDivisor: 0 //  Do not instantiate drawing 
   }
 ]
 Copy code 

call Buffer Static methods of private classes createVertexBuffer(), You can create a built-in WebGLBuffer Vertex buffer object positionBuffer, Then use the ordinary object array to create Vertex Attribute attributes, Each object describes a vertex attribute . Then you can take these simple materials to create VertexArray 了 :

 const va = new VertexArray({
   context: context,
   attributes: attributes
 })
 Copy code 

Context Encapsulates the WebGL Various function calls , You can start your Scene Or directly from FrameState Get to .

This step creates Buffer, Vertex coordinates are in rectangular coordinates , Is the original coordinate value , Unless you do matrix transformation in the shader , Or these rectangular coordinates are near the surface of the world coordinate system . It's a bunch of things without specific semantics 、 Mathematical pure geometric coordinates , It has nothing to do with rendering pipelines . therefore , For a coordinate point somewhere on the surface , Usually to cooperate with ENU Transformation matrix + Built in MVP Transform matrix to use , see 1.6 Example .

Here's another example , Two vertex attributes are used (VertexAttribute):

 const positionBuffer = Buffer.createVertexBuffer({
   context: context,
   sizeInBytes: 12,
   usage: BufferUsage.STATIC_DRAW
 })
 const normalBuffer = Buffer.createVertexBuffer({
   context: context,
   sizeInBytes: 12,
   usage: BufferUsage.STATIC_DRAW
 })
 const attributes = [
   {
     index: 0,
     vertexBuffer: positionBuffer,
     componentsPerAttribute: 3,
     componentDatatype: ComponentDatatype.FLOAT
   },
   {
     index: 1,
     vertexBuffer: normalBuffer,
     componentsPerAttribute: 3,
     componentDatatype: ComponentDatatype.FLOAT
   }
 ]
 const va = new VertexArray({
   context: context,
   attributes: attributes
 })
 Copy code 

Here, the coordinate buffer and normal buffer are stored in two objects separately , Actually WebGL You can use byte interleaved format , Merge the buffers of all vertex attributes into one , Let's not be specific , Readers can refer to WebGL in WebGLBuffer Usage of .

1.2. Constituent elements - ShaderProgram

WebGL The shader is also CesiumJS Encapsulates the , Built in caching mechanism , And use a large number of regular methods to do shader source code matching 、 analysis 、 management .

Shader code consists of ShaderSource management ,ShaderProgram Manage multiple shader source codes , That is, the shader itself . Use ShaderCache As a cache container for shader programs . Their hierarchical relationship is as follows :

 Context
   ┖ ShaderCache
     ┖ ShaderProgram
       ┖ ShaderSource
 Copy code 

You can create your own ShaderSourceShaderProgram, And pass Context Add to ShaderCache in .

give an example :

 new ShaderSource({
   sources : [GlobeFS]
 })
 ​
 new ShaderProgram({
   gl: context._gl,
   logShaderCompilation: context.logShaderCompilation,
   debugShaders: context.debugShaders,
   vertexShaderSource: vertexShaderSource,
   vertexShaderText: vertexShaderText,
   fragmentShaderSource: fragmentShaderSource,
   fragmentShaderText: fragmentShaderText,
   attributeLocations: attributeLocations,
 })
 Copy code 

But usually choose a more direct way :

 const vertexShaderText = `attribute vec3 position;
 void main() {
   gl_Position = czm_projection * czm_modelView * vec4(position, 1.0);
 }`
 const fragmentShaderText = `uniform vec3 color;
 void main() {
   gl_FragColor=vec4( color , 1. );
 }`
 ​
 const program = ShaderProgram.fromCache({
   context: context,
   vertexShaderSource: vertexShaderText,
   fragmentShaderSource: fragmentShaderText,
   attributeLocations: attributeLocations
 })
 Copy code 

Use ShaderProgram.fromCache Static methods will automatically help you cache shaders to ShaderCache In the container .

Shader code can directly use built-in constants and automatically unify values , This will be added by default .

attributeLocation What is it? ? It's a very common JavaScript object :

 {
   "position": 0,
   "normal": 1,
   "st": 2,
   "bitangent": 3,
   "tangent": 4,
   "color": 5
 }
 Copy code 

It indicates the position of the vertex attribute in the shader .

1.3. Constituent elements - WebGL Uniform value of

This is simpler :

 const uniforms = {
   color() {
     return Cesium.Color.HONEYDEW 
   }
 }
 Copy code 

Use one JavaScript Object can , Each member must be Method , The returned value matches Uniform Just meet your requirements :

  • Cesium.Matrix2/3/4mat2/3/4
  • Cesium.Cartesian2/3/4vec2/3/4
  • Cesium.Numberfloat
  • Cesium.Colorvec4
  • Cesium.Texturesampler2D
  • ...

Please refer to Renderer/createUniform.js The code in , for example UniformFloatVec3 It can correspond to Color and Cartesian4 wait .

This uniforms The object will end up in Context When drawing , Automatic unified value with the system (AutomaticUniforms) Merge .

 Context.prototype.draw = function (/*...*/) {
   // ...
   continueDraw(this, drawCommand, shaderProgram, uniformMap);
   // ...
 }
 Copy code 

1.4. Render state objects - RenderState

Render state objects must be passed to DrawCommand Of . The render state object type is RenderState, It is associated with ShaderProgram similar , Both provide static methods to “ Cache type ” establish :

 const renderState = RenderState.fromCache({
   depthTest: {
     enabled: true
   }
 })
 Copy code 

Even if nothing is passed :RenderState.fromCache(), The interior will also return a rendering state .

It passes everything except rendering data WebGL Rendered state values , stay RenderState There is a detailed default list reference in , The above code explicitly specifies the depth test to be performed .

1.5. Other components

Create drawing instructions except 1.1 ~ 1.3 In addition to the ingredients , There are other options .

① The type of channel drawn - Pass

CesiumJS It's not rough to put... On the frame state object Command Traverse once and draw , stay Scene In the process of rendering , In addition to generating three Command, One more step is to Command Sort channels .

passageway , Is an enumerated type , Save in Pass.js Module . Different channels have different priorities , For example, in 1.6 The specified channel is Cesium.Pass.OPAQUE, Opaque channel . stay 1.93 edition , The order of channels is enumerated values :

 const Pass = {
   ENVIRONMENT: 0,
   COMPUTE: 1,
   GLOBE: 2,
   TERRAIN_CLASSIFICATION: 3,
   CESIUM_3D_TILE: 4,
   CESIUM_3D_TILE_CLASSIFICATION: 5,
   CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW: 6,
   OPAQUE: 7,
   TRANSLUCENT: 8,
   OVERLAY: 9,
   NUMBER_OF_PASSES: 10,
 }
 Copy code 

so ,OPAQUE ( Opaque channel ) The priority ratio TRANSLUCENT( Transparent channel ) high .

This channel and other graphics API The channel may be slightly different , Because you can only use this value to specify the order , Instead of writing a channel to synthesize rendering ( for example ThreeJS or WebGPU).

② Type of element drawn - WebGL Draw constant

Designated VertexArray Topological format of vertices in , stay WebGL China is through drawArrays designated :

 gl.drawArrays(gl.TRIANGLES, 0, 3)
 Copy code 

This gl.TRIANGLES Is the element type , It's a constant .Cesium All in PrimitiveType.js In the enumeration exported by the module :

 console.log(PrimitiveType.TRIANGLES) // 4
 Copy code 

The default is PrimitiveType.TRIANGLES, So in 1.6 In the code, we don't need to pass .

③ Off screen drawing container - Framebuffer

CesiumJS Support drawing the results to Renderbuffer, That is to say RTR(Render to RenderBuffer) Draw off screen . Draw to render buffer , It needs a frame buffer container ,CesiumJS hold WebGL 1/2 Related to frame buffer API It's all packaged ( Strictly speaking , hold WebGL Medium API Basically encapsulated once ).

This article only briefly mentions , About frame buffer off screen drawing , I will introduce it later , Fake chicken silk blog has a more systematic introduction ( Although relatively old , But the idea is still ).

④ Model coordinate transformation matrix - Matrix4

take Matrix4 Variables of type are created DrawCommand When you pass it in , It will eventually pass to CesiumJS Internal uniform value of :czm_model( Model matrix ) On , Without you uniform It is specified in , You can use it in vertex shaders to VertexArray The vertices in the model matrix are transformed . see 1.6 Vertex shader in classic MVP Multiply .

⑤ Other

  • cull/occlude: Cone culling + Horizon culling combination ,Boolean
  • orientedBoundingBox/boundingVolume: Scope box
  • count: number,WebGL How many points to draw
  • offset: number,WebGL When drawing, start with vertex data at the offset
  • instanceCount: number, Instance drawing
  • castShadows/receiveShadows: Boolean, Shadow related
  • pickId: string, If not defined , stay Pick The depth data will be used in the drawing of the channel ; If defined, it will be in GLSL It turns into pick id
  • ...

All of these can be in DrawCommand Find the corresponding field in , Set as needed .

1.6. Let's practice a solid triangle

everything , Directly hard rub a that can generate triangle drawing instructions StaticTrianglePrimitive, For ease of use in the official sandbox , I'll give it to the official API Added namespace :

 const modelCenter = Cesium.Cartesian3.fromDegrees(112, 23, 0)
 const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(modelCenter)
 ​
 const vertexShaderText = `attribute vec3 position;
 void main() {
   gl_Position = czm_projection * czm_view * czm_model * vec4(position, 1.0);
 }`
 const fragmentShaderText = `uniform vec3 u_color;
 void main(){
   gl_FragColor = vec4(u_color, 1.0);
 }`
 ​
 const createCommand = (frameState, matrix) => {
   const attributeLocations = {
     "position": 0,
   }
   const uniformMap = {
     u_color() {
       return Cesium.Color.HONEYDEW
     },
   }
   const positionBuffer = Cesium.Buffer.createVertexBuffer({
     usage: Cesium.BufferUsage.STATIC_DRAW,
     typedArray: new Float32Array([
       10000, 50000, 5000,
       -20000, -10000, 5000,
       50000, -30000, 5000,
     ]),
     context: frameState.context,
   })
   const vertexArray = new Cesium.VertexArray({
     context: frameState.context,
     attributes: [{
       index: 0, //  be equal to  attributeLocations['position']
       vertexBuffer: positionBuffer,
       componentsPerAttribute: 3,
       componentDatatype: Cesium.ComponentDatatype.FLOAT
     }]
   })
   const program = Cesium.ShaderProgram.fromCache({
     context: frameState.context,
     vertexShaderSource: vertexShaderText,
     fragmentShaderSource: fragmentShaderText,
     attributeLocations: attributeLocations,
   })
   const renderState = Cesium.RenderState.fromCache({
     depthTest: {
       enabled: true
     }
   })
   return new Cesium.DrawCommand({
     modelMatrix: matrix,
     vertexArray: vertexArray,
     shaderProgram: program,
     uniformMap: uniformMap,
     renderState: renderState,
     pass: Cesium.Pass.OPAQUE,
   })
 }
 ​
 /* ----- See Here ↓ ------ */
 ​
 class StaticTrianglePrimitive {
   /**
    * @param {Matrix4} modelMatrix matrix to WorldCoordinateSystem
    */
   constructor(modelMatrix) {
     this._modelMatrix = modelMatrix
   }
   
   /**
    * @param {FrameState} frameState
    */
   update(frameState) {
     const command = createCommand(frameState, this._modelMatrix)
     frameState.commandList.push(command)
   }
 }
 ​
 // try!
 const viewer = new Cesium.Viewer('cesiumContainer', {
   contextOptions: {
     requestWebgl2: true
   }
 })
 viewer.scene.globe.depthTestAgainstTerrain = true
 viewer.scene.primitives.add(new StaticTrianglePrimitive(modelMatrix))
 Copy code 

The displayed effect is a white green triangle :

image-20220428150841888.png

The picture shows Dawan District , Because I set ENU The coordinate center is near Dawan district . I set the height of the triangle to 5000 rice .

2. significance - Customize Primitive(PrimitiveLike)

If there is an object or a function , Returns a paintable DrawCommand, Then just pass the returned instruction object to FrameState The above data and drawing logic can be displayed in this frame .

Think carefully , Have the ability to create DrawCommand In fact, there are many objects . Yes PrimitiveBillboardCollectionSkyAtmosphereSkyBoxSunModel etc. (3DTiles The model on the tile is through Model Draw the ).

I'll come to a conclusion here :

  • Have the ability to create DrawCommand Functional , Whether it's a function , Or object , Can directly participate in Cesium The bottom drawing ;
  • The prototype chain has update Class of method , And update Method to accept a FrameState object , And add... To the frame state object during execution DrawCommand Of , Can be added to scene.primitives This PrimitiveCollection in .

The former has specific API, namely Globe Under the GlobeSurfaceTileProvider( from QuadtreePrimitive Use ) establish DrawCommand; There are more in the back .

Can accurately control DrawCommand, You can go to Cesium Do what you want to do in the scene .

nudges

DrawCommand yes CesiumJS The last data encapsulation before the renderer , The next step is to distribute the resources on these instruction objects 、 binding 、 perform . If the reader is interested , You can also study it yourself ClearCommand and ComputeCommand, Maybe later I will write , But so far in this article ~

3. Reference material

copyright notice
author[Songs in the four seasons],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/04/202204291312384782.html

Random recommended