Cesium渲染模組之VAO

當時明月在曾照彩雲歸發表於2023-02-28

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渲染模組的VAO

2. WebGL中的VAO

以下大致是一個最簡的WebGL繪製程式碼:

<canvas id="canvas"></canvas>
<script>
    const vertexSource = `
                attribute vec3 aPos;

                void main()
                {
                    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
                }
              `
    const fragmentSource = `
                void main()
                {
                    gl_FragColor = vec4(1.0, 0.5, 0.2, 1.0);
                }
              `
    const canvas = document.getElementById('canvas');
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    const gl = canvas.getContext('webgl2');
    if (!gl) {
        alert('WebGL not supported');
    }

    const vertices = new Float32Array([
        -0.5, -0.5, 0.0,
        0.5, -0.5, 0.0,
        0.0, 0.5, 0.0,
    ]);
    const vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

    const vao = gl.createVertexArray();
    gl.bindVertexArray(vao);
    gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(0)

    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexSource);
    gl.compileShader(vertexShader);

    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentSource);
    gl.compileShader(fragmentShader);

    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    gl.clearColor(0.2, 0.3, 0.3, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(shaderProgram);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

</script>

image-20230227214406870

其中,vao是頂點陣列物件(Vertex Array Object, VAO),主要用來儲存屬性資訊:

const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0)

WebGLVertexArrayObject介面是WebGL 2 API的一部分,頂點陣列物件 (VAOs) 指向頂點陣列資料,並提供不同頂點資料集合的名稱

當使用WebGLVertexArrayObject物件時,這些方法會很有用:

示例程式碼中:

createVertexArray()方法建立並初始化(creates and initializes)一個 WebGLVertexArrayObject 的物件 (object) ,它代表一個指向頂點陣列資料的頂點陣列物件(vertex array object (VAO) ),併為不同的頂點資料集提供名稱

bindVertexArray(target) 方法將給定的VAO繫結(後續對VBO的操作將會記錄在繫結的這個VAO上),引數:

vertexAttribPointer(index, size, type, normalized, stride, offset) 方法繫結當前繫結的Buffer到到當前頂點的通用頂點屬性緩衝區物件(VAO)並指定其佈局(告訴顯示卡從當前繫結的緩衝區(bindBuffer() 指定的緩衝區)中怎麼讀取頂點資料),引數:

  • index

    GLuint (en-US) 指定要修改的頂點屬性的索引

  • size

    GLint (en-US) 指定每個頂點屬性的組成數量,必須是 1,2,3 或 4

  • type

    GLenum (en-US) 指定陣列中每個元素的資料型別可能是:

    • gl.BYTE: 有符號的 8 位整數,範圍 [-128, 127]

    • gl.SHORT: 有符號的 16 位整數,範圍 [-32768, 32767]

    • gl.UNSIGNED_BYTE: 無符號的 8 位整數,範圍 [0, 255]

    • gl.UNSIGNED_SHORT: u 無符號的 16 位整數,範圍 [0, 65535]

    • gl.FLOAT: 32 位 IEEE 標準的浮點數

      使用 WebGL2 版本的還可以使用以下值:

      • gl.HALF_FLOAT: 16-bit IEEE floating point number 16 位 IEEE 標準的浮點數
  • normalized

    GLboolean (en-US) 當轉換為浮點數時是否應該將整數數值歸一化到特定的範圍

​ 對於型別gl.BYTEgl.SHORT,如果是 true 則將值歸一化為 [-1, 1]

​ 對於型別gl.UNSIGNED_BYTEgl.UNSIGNED_SHORT,如果是 true 則將值歸一化為 [0, 1]

​ 對於型別gl.FLOATgl.HALF_FLOAT,此引數無效

  • stride

    一個 GLsizei (en-US),以位元組為單位指定連續頂點屬性開始之間的偏移量 (即陣列中一行長度)。不能大於 255。如果 stride 為 0,則假定該屬性是緊密打包的,即不交錯屬性,每個屬性在一個單獨的塊中,下一個頂點的屬性緊跟當前頂點之後

  • offset

    GLintptr (en-US)指定頂點屬性陣列中第一部分的位元組偏移量,必須是型別的位元組長度的倍數

以上大致就是WebGL中vertex array object (VAO)的用法,需要注意,VAO物件在webgl2中才支援,在webgl1中是擴充套件的

3. Cesium中的VAO

Cesium中,對WebGL的VAO進行了封裝:

function VertexArray(options) {
    // ...
    const context = options.context;
    const gl = context._gl;
    const attributes = options.attributes;
    const indexBuffer = options.indexBuffer;
 
    let vao;
    vao = context.glCreateVertexArray();
    context.glBindVertexArray(vao);
    bind(gl, vaAttributes, indexBuffer);
    context.glBindVertexArray(null);
}

建立一個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
});

建立一個VAO和兩個VBO的例子:

// Example 2. Create a vertex array with vertices from two different vertex buffers.
// Each vertex has a three-component position and three-component normal.
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
});

建立一個VAO與合併的兩個VBO的例子:

// Example 3. Creates the same vertex layout as Example 2 using a single
// vertex buffer, instead of two.
const buffer = Buffer.createVertexBuffer({
    context : context,
    sizeInBytes : 24,
    usage : BufferUsage.STATIC_DRAW
});
const attributes = [
    {
        vertexBuffer           : buffer,
        componentsPerAttribute : 3,
        componentDatatype      : ComponentDatatype.FLOAT,
        offsetInBytes          : 0,
        strideInBytes          : 24
    },
    {
        vertexBuffer           : buffer,
        componentsPerAttribute : 3,
        componentDatatype      : ComponentDatatype.FLOAT,
        normalize              : true,
        offsetInBytes          : 12,
        strideInBytes          : 24
    }
];
const va = new VertexArray({
    context : context,
    attributes : attributes
});

Cesium中的VAO還支援從Geometry建立:

// ...
// Create geometry with a position attribute and indexed lines.
const positions = new Float64Array([
  0.0, 0.0, 0.0,
  7500000.0, 0.0, 0.0,
  0.0, 7500000.0, 0.0
]);

const geometry = new Cesium.Geometry({
  attributes : {
    position : new Cesium.GeometryAttribute({
      componentDatatype : Cesium.ComponentDatatype.DOUBLE,
      componentsPerAttribute : 3,
      values : positions
    })
  },
  indices : new Uint16Array([0, 1, 1, 2, 2, 0]),
  primitiveType : Cesium.PrimitiveType.LINES,
  boundingSphere : Cesium.BoundingSphere.fromVertices(positions)
});
// ...

// Example 1. Creates a vertex array for rendering a box.  The default dynamic draw
// usage is used for the created vertex and index buffer.  The attributes are not
// interleaved by default.
const geometry = new BoxGeometry();
const va = VertexArray.fromGeometry({
    context            : context,
    geometry           : geometry,
    attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
});

// Example 2. Creates a vertex array with interleaved attributes in a
// single vertex buffer.  The vertex and index buffer have static draw usage.
const va = VertexArray.fromGeometry({
    context            : context,
    geometry           : geometry,
    attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
    bufferUsage        : BufferUsage.STATIC_DRAW,
    interleave         : true
});

在Cesium原始碼中建立VAO大多是直接使用建構函式,例如PolylineCollection.js

const va = new VertexArray({
          context: context,
          attributes: attributes,
          indexBuffer: indexBuffer,
        });

此外,VertexArray封裝的函式有:

function addAttribute(attributes, attribute, index, context)
function interleaveAttributes(attributes)
function setVertexAttribDivisor(vertexArray)
function setConstantAttributes(vertexArray, gl)
VertexArray.prototype._bind = function ()
VertexArray.prototype._unBind = function ()
VertexArray.prototype.isDestroyed = function ()
VertexArray.prototype.destroy = function ()

4. 參考資料

[1]WebGL2RenderingContext - Web API 介面參考 | MDN (mozilla.org)

[2]Cesium DrawCommand 不談地球 畫個三角形 - 四季留歌 - 部落格園 (cnblogs.com)

[3]Cesium原理篇:6 Render模組(5: VAO&RenderState&Command) - fu*k - 部落格園 (cnblogs.com)

[4]Cesium渲染模組之概述 - 當時明月在曾照彩雲歸 - 部落格園 (cnblogs.com)

[5]Cesium渲染模組之Buffer - 當時明月在曾照彩雲歸 - 部落格園 (cnblogs.com)

相關文章