0. Preface
Primitive API
is the bottom layer of the public API, which is oriented towards high-performance, customizable material shaders ( Appearance API + FabricMaterial Specification
), and static 3D objects.
Despite this, Primitive API
still encapsulates a large number of geometry classes, material classes, WebWorker, and currently only the new architecture of the 3D model class is open to the custom shader API, which has not been distributed to Primitive API
.
If the API burden is not so heavy, and you want to use your own model format (it must be triangular), then the private DrawCommand + VertexArray
interface is very suitable, and its style is already the closest to the bottom layer of CesiumJS WebGL. class API too.
DrawCommand
is an excellent design of Cesium to encapsulate WebGL, it uses drawing data ( VertexArray
) and drawing behavior ( ShaderProgram
) as an object That is, when Scene
executes the executeCommand
function, all the instruction objects on the frame state object will be executed using the WebGL function, and you can bind whatever you want, so that the usage in drawing is consistent. The upper application interface only needs to generate the instruction object.
0.1. DrawCommand in the source code
For instance, in Primitive.js
module createCommands
function, which is responsible Primitive
parametric data or calculations WebWorker object data generation combined DrawCommand
where:
function createCommands(/* 参数省略 */) {
// ...
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, // 入参,即 Primitive 对象
primitiveType: primitive._primitiveType,
});
}
colorCommand.vertexArray = primitive._va[vaIndex]; // VertexArray
colorCommand.renderState = primitive._frontFaceRS; // 渲染状态
colorCommand.shaderProgram = primitive._sp; // ShaderProgram
colorCommand.uniformMap = uniforms; // 统一值
colorCommand.pass = pass; // 该指令的通道顺序
}
// ...
}
1. Create
1.1. Components - VertexArray
Cesium wraps WebGL's vertex buffer and index buffer into Buffer
, and then binds these vertex-related buffers in an object for convenience, called VertexArray
, which will be enabled internally WebGL's VAO
function.
The fastest way to create VertexArray
is to call its static method VertexArray.fromGeometry()
, but this requires Geometry API
to help.
If you want to use Buffer
directly to illustrate, then you have to create Buffer
first:
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, // 紧密组合在一起,没有 byteStride
instanceDivisor: 0 // 不实例化绘制
}
]
Call Buffer
static method of the private class createVertexBuffer()
, you can create a built- WebGLBuffer
vertex buffer objects positionBuffer
, then use ordinary objects The array creates vertex attributes attributes
, and each object describes a vertex attribute. Next, you can create these simple materials VertexArray
:
const va = new VertexArray({
context: context,
attributes: attributes
})
Context
encapsulates various function calls of WebGL, you can get it from Scene
or directly from FrameState
.
Created in this step Buffer
, the vertex coordinates are in the Cartesian coordinate system, which is the most primitive coordinate value, unless matrix transformation is done in the shader, or these Cartesian coordinates are near the surface of the world coordinate system. It is a bunch of coordinates with no specific semantics, purely mathematical geometry, independent of the rendering pipeline. Therefore, for a coordinate point somewhere on the surface, it is usually used in conjunction with the ENU transformation matrix + the built-in MVP transformation matrix, see the example in 1.6.
Here's another example, using two VertexAttributes:
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
})
Here, the coordinate buffer and the normal buffer are stored separately in two objects. In fact, WebGL can use the byte-interleaved format to combine all the vertex attribute buffers into one, so I won't go into details. Readers can do it by themselves. Check out the usage of WebGLBuffer in WebGL.
1.2. Components - ShaderProgram
The shader of WebGL is also encapsulated by CesiumJS, which has its own caching mechanism, and uses a large number of regular methods to match, parse and manage shader source code.
The shader code is managed by ShaderSource
, and ShaderProgram
manages 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
You can create your own ShaderSource
, ShaderProgram
and add ---3f91880994ddaa72a06aee5bb4ddbfe1 Context
to ShaderCache
.
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,
})
But usually the more direct way is chosen:
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
})
Using the ShaderProgram.fromCache
static method will automatically cache the shader for you in the ShaderCache
container.
Shader code can directly use built-in constants and auto-uniform values, which are added by default.
attributeLocation
what is it? It's a very plain JavaScript object:
{
"position": 0,
"normal": 1,
"st": 2,
"bitangent": 3,
"tangent": 4,
"color": 5
}
It indicates the location of the vertex attribute in the shader.
1.3. Constituents - WebGL Uniform Values
This one is relatively simple:
const uniforms = {
color() {
return Cesium.Color.HONEYDEW
}
}
Just use a JavaScript object, each member must be a method , and the returned value meets the requirements of Uniform:
-
Cesium.Matrix2/3/4
→mat2/3/4
-
Cesium.Cartesian2/3/4
→vec2/3/4
-
Cesium.Number
→float
-
Cesium.Color
→vec4
-
Cesium.Texture
→sampler2D
- ...
Please check the codes in ---724116bd2a722a617a55706d533daf35 Renderer/createUniform.js
, for example UniformFloatVec3
can correspond to Color
and Cartesian4
and so on.
This uniforms
object will eventually be merged with the system's automatic unity value ( AutomaticUniforms
) when the Context
is drawn.
Context.prototype.draw = function (/*...*/) {
// ...
continueDraw(this, drawCommand, shaderProgram, uniformMap);
// ...
}
1.4. Render State Object - RenderState
The render state object must be passed to DrawCommand
. The render state object type is RenderState
, which is similar to ShaderProgram
and provides static methods for "cached" creation:
const renderState = RenderState.fromCache({
depthTest: {
enabled: true
}
})
Even if nothing is passed: RenderState.fromCache()
, a render state is returned internally.
It passes all state values that participate in WebGL rendering except rendering data. There is a detailed default list reference in RenderState
The above code explicitly specifies that depth testing is to be performed.
1.5. Other constituent factors
In addition to the components 1.1 ~ 1.3, there are other options for creating drawing instructions.
① Drawn channel type - Pass
CesiumJS does not rudely traverse the Command on the frame state object and draw it. In the rendering process of Scene, in addition to generating the three Commands, there is a step to sort the Command channels.
Channel, an enumeration type, is stored in the Pass.js
module. Different channels have different priorities. For example, the channel specified in 1.6 is Cesium.Pass.OPAQUE
, that is, an opaque channel. In version 1.93, the order of the channels is the enumeration value:
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,
}
It can be seen that OPAQUE
(opaque channel) has higher priority than TRANSLUCENT
(transparent channel).
This channel may be slightly different from other graphics API channels, because you can only use this value to specify the order, rather than writing a channel yourself to composite rendering (such as ThreeJS or WebGPU).
② Drawing primitive type - WebGL drawing constant
That is to say, the topology format of the vertices in the specified VertexArray
is specified by drawArrays
8aface3fcde2300a3220d566cd48cf75--- in WebGL:
gl.drawArrays(gl.TRIANGLES, 0, 3)
This gl.TRIANGLES
is the primitive type, which is a constant. Cesium is all encapsulated in the enumeration exported by the PrimitiveType.js
module:
console.log(PrimitiveType.TRIANGLES) // 4
The default is PrimitiveType.TRIANGLES
, so in the 1.6 code we don't need to pass it.
③ Off-screen drawing container - Framebuffer
CesiumJS supports drawing the result to Renderbuffer
, which is RTR(Render to RenderBuffer)
off-screen drawing. Drawing to the rendering buffer requires a frame buffer container. CesiumJS encapsulates the frame buffer related APIs in WebGL 1/2 (strictly speaking, the APIs in WebGL are basically encapsulated).
This article only briefly mentions that about frame buffer off-screen drawing, I will introduce it later when I have the opportunity. Fake Jisi's blog has a more systematic introduction (although it is relatively old, but the idea is still there).
④ Model coordinate transformation matrix - Matrix4
Matrix4
a variable of type DrawCommand
, it will eventually be passed to CesiumJS's internal uniform value: czm_model
(model matrix), without needing You specify in uniform
and you can use it in a vertex shader to model matrix transforms on vertices in VertexArray
. See vertex shader classic MVP multiplication in 1.6.
⑤ Others
- cull/occlude: frustum culling + horizon culling combo, Boolean
- orientedBoundingBox/boundingVolume: bounding box
- count: number, how many points to draw when WebGL draws
- offset: number, the offset from which to start using vertex data when WebGL draws
- instanceCount: number, instance drawing related
- castShadows/receiveShadows: Boolean, shadow related
- pickId: string, if not defined, the depth data will be used in the drawing of the Pick channel; if defined, it will be converted to pick id in GLSL
- ...
You can find the corresponding fields in DrawCommand
, and you can set them as needed.
1.6. Let's practice a solid color triangle
Everything is ready, directly rub a triangle drawing instruction StaticTrianglePrimitive
, in order to facilitate use in the official sandbox, I added a namespace to the official API:
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, // 等于 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))
The displayed effect is a white-green triangle:
The picture shows the Greater Bay Area, because the ENU coordinate center I set is near the Greater Bay Area. The height of the triangle is set to 5000 meters by me.
2. Meaning - Custom Primitive (PrimitiveLike)
If there is an object or a function that returns a drawable DrawCommand
, then just pass the returned instruction object to FrameState
, you can combine the above data and The drawing logic is shown.
If you think about it, there are actually quite a few objects capable of creating DrawCommand
. There Primitive
, BillboardCollection
, SkyAtmosphere
, SkyBox
, Sun
, Model
, etc. ( Models on 3DTiles tiles are drawn by Model
).
I'll just come to the conclusion here:
- With the function of creating
DrawCommand
, whether it is a function or an object, it can directly participate in the bottom drawing of Cesium; - The prototype chain has a class with
update
method, and theupdate
method accepts aFrameState
object, and addsDrawCommand
to this frame state object during executionDrawCommand
, you can add it toscene.primitives
thisPrimitiveCollection
.
The former has a specific API, namely Globe
under GlobeSurfaceTileProvider
(used by QuadtreePrimitive
) to create DrawCommand
; .
With precise control DrawCommand
, you can do the drawing you want in the Cesium scene.
click till
DrawCommand
is the last data encapsulation before the CesiumJS renderer, which is followed by the distribution, binding and execution of the resources on these instruction objects. If readers are interested, they can also study ClearCommand
and ComputeCommand
, and maybe they will write it later, but this article will stop~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。