WebGLRenderingContext: vertexAttribPointer() 方法

Baseline 已廣泛支援

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2015 年 7 月⁩以來,各瀏覽器均已提供此特性。

注意:此功能在 Web Workers 中可用。

WebGL API 的 WebGLRenderingContext.vertexAttribPointer() 方法將當前繫結到 gl.ARRAY_BUFFER 的緩衝區繫結到當前頂點緩衝區物件的通用頂點屬性,並指定其佈局。

語法

js
vertexAttribPointer(index, size, type, normalized, stride, offset)

引數

index

一個 GLuint,指定要修改的頂點屬性的索引。

size

一個 GLint,指定每個頂點屬性的元件數量。必須是 1、2、3 或 4。

type

一個 GLenum,指定陣列中每個元件的資料型別。可能的值:

  • gl.BYTE:有符號 8 位整數,值範圍 [-128, 127]
  • gl.SHORT:有符號 16 位整數,值範圍 [-32768, 32767]
  • gl.UNSIGNED_BYTE:無符號 8 位整數,值範圍 [0, 255]
  • gl.UNSIGNED_SHORT:無符號 16 位整數,值範圍 [0, 65535]
  • gl.FLOAT:32 位 IEEE 浮點數

使用 WebGL 2 上下文時,還可以使用以下值:

  • gl.HALF_FLOAT:16 位 IEEE 浮點數
  • gl.INT:32 位有符號二進位制整數
  • gl.UNSIGNED_INT:32 位無符號二進位制整數
  • gl.INT_2_10_10_10_REV:32 位有符號整數,值範圍 [-512, 511]
  • gl.UNSIGNED_INT_2_10_10_10_REV:32 位無符號整數,值範圍 [0, 1023]
normalized(規範化)

一個 GLboolean,指定當整數資料值轉換為浮點數時,是否應將其規範化為特定範圍。

  • 對於 gl.BYTEgl.SHORT 型別,如果為 true,則將值規範化為 [-1, 1]。
  • 對於 gl.UNSIGNED_BYTEgl.UNSIGNED_SHORT 型別,如果為 true,則將值規範化為 [0, 1]。
  • 對於 gl.FLOATgl.HALF_FLOAT 型別,此引數無效。
stride(步幅)

一個 GLsizei,指定連續頂點屬性之間以位元組為單位的偏移量。不能為負數或大於 255。如果步幅為 0,則假定屬性是緊密打包的,也就是說,屬性不交錯,而是每個屬性都在一個單獨的塊中,並且下一個頂點的屬性緊跟在當前頂點之後。

offset

一個 GLintptr,指定頂點屬性陣列中第一個元件的位元組偏移量。必須是 type 的位元組長度的倍數。

返回值

無(undefined)。

異常

  • 如果 strideoffset 為負數,則丟擲 gl.INVALID_VALUE 錯誤。
  • 如果 strideoffset 不是資料型別大小的倍數,則丟擲 gl.INVALID_OPERATION 錯誤。
  • 如果沒有 WebGLBuffer 繫結到 ARRAY_BUFFER 目標,則丟擲 gl.INVALID_OPERATION 錯誤。
  • 使用 WebGL 2 上下文時,如果此頂點屬性在頂點著色器中定義為整數(例如 uvec4ivec4,而不是 vec4),則會丟擲 gl.INVALID_OPERATION 錯誤。

描述

假設我們想渲染一些 3D 幾何體,為此我們需要將頂點提供給頂點著色器。每個頂點都有一些屬性,如位置、法向量或紋理座標,它們定義在一個 ArrayBuffer 中,並將提供給頂點緩衝區物件 (VBO)。首先,我們需要將要使用的 WebGLBuffer 繫結到 gl.ARRAY_BUFFER,然後,使用此方法 gl.vertexAttribPointer(),我們指定屬性的儲存順序和資料型別。此外,我們需要包含步幅,它是單個頂點的所有屬性的總位元組長度。此外,我們必須呼叫 gl.enableVertexAttribArray() 來告訴 WebGL 這個屬性應該從我們的陣列緩衝區中填充資料。

通常,您的 3D 幾何體已經採用某種二進位制格式,因此您需要閱讀該特定格式的規範以瞭解記憶體佈局。但是,如果您自己設計格式,或者您的幾何體在文字檔案中(例如 Wavefront .obj 檔案),並且必須在執行時轉換為 ArrayBuffer,您可以自由選擇如何組織記憶體。為了獲得最高效能,交錯屬性並使用仍然能準確表示幾何體的最小資料型別。

頂點屬性的最大數量取決於顯示卡,您可以呼叫 gl.getParameter(gl.MAX_VERTEX_ATTRIBS) 來獲取此值。在高階顯示卡上,最大值為 16,在低端顯示卡上,該值會更低。

屬性索引

對於每個屬性,您必須指定其索引。這與陣列緩衝區中的位置無關,因此您的屬性可以以與它們在陣列緩衝區中儲存的順序不同的順序傳送。您有兩種選擇:

  • 您可以自己指定索引。在這種情況下,您呼叫 gl.bindAttribLocation() 將頂點著色器中命名的屬性連線到您想要使用的索引。這必須在呼叫 gl.linkProgram() 之前完成。然後,您可以將此相同的索引提供給 gl.vertexAttribPointer()
  • 或者,您可以使用顯示卡在編譯頂點著色器時分配的索引。根據顯示卡的不同,索引會有所不同,因此您必須呼叫 gl.getAttribLocation() 來查詢索引,然後將此索引提供給 gl.vertexAttribPointer()。如果您使用 WebGL 2,您可以在頂點著色器程式碼中自行指定索引並覆蓋顯示卡使用的預設值,例如 layout(location = 3) in vec4 position; 將把 "position" 屬性設定為索引 3。

整數屬性

雖然 ArrayBuffer 可以填充整數和浮點數,但當屬性發送到頂點著色器時,它們將始終轉換為浮點數。如果需要在頂點著色器程式碼中使用整數,您可以在頂點著色器中將浮點數轉換回整數(例如 (int) floatNumber),或者使用 WebGL2 的 gl.vertexAttribIPointer()

預設屬性值

頂點著色器程式碼可能包含許多屬性,但我們不需要為每個屬性指定值。相反,我們可以提供一個對所有頂點都相同的預設值。我們可以呼叫 gl.disableVertexAttribArray() 來告訴 WebGL 使用預設值,而呼叫 gl.enableVertexAttribArray() 將從陣列緩衝區中讀取值,如 gl.vertexAttribPointer() 所指定。

類似地,如果我們的頂點著色器期望例如一個帶有 vec4 的 4 分量屬性,但在我們的 gl.vertexAttribPointer() 呼叫中我們將 size 設定為 2,那麼 WebGL 將根據陣列緩衝區設定前兩個分量,而第三和第四個分量將取自預設值。

預設值為 vec4(0.0, 0.0, 0.0, 1.0),但我們可以使用 gl.vertexAttrib[1234]f[v]() 指定不同的預設值。

例如,您的頂點著色器可能使用位置和顏色屬性。大多數網格以每個頂點級別指定顏色,但有些網格具有統一的色調。對於這些網格,不需要將相同的顏色放入陣列緩衝區中的每個頂點,因此您使用 gl.vertexAttrib4fv() 設定常量顏色。

查詢當前設定

您可以呼叫 gl.getVertexAttrib()gl.getVertexAttribOffset() 來獲取屬性的當前引數,例如資料型別或屬性是否應該被規範化。請記住,這些 WebGL 函式的效能較慢,最好將狀態儲存在 JavaScript 應用程式中。但是,這些函式非常適合在不觸及應用程式程式碼的情況下除錯 WebGL 上下文。

示例

此示例展示瞭如何將頂點屬性發送到著色器程式。我們使用一個假想的資料結構,其中每個頂點的屬性以交錯方式儲存,每個頂點長度為 20 位元組

  1. 位置:我們需要儲存 X、Y 和 Z 座標。為了獲得最高精度,我們使用 32 位浮點數;總共使用 12 位元組。
  2. 法向量:我們需要儲存法向量的 X、Y 和 Z 分量,但由於精度不那麼重要,我們使用 8 位有符號整數。為了獲得更好的效能,我們透過額外儲存一個值為零的第四個分量將資料對齊到 32 位,使總大小達到 4 位元組。此外,我們告訴 WebGL 規範化這些值,因為我們的法線始終在 [-1, 1] 範圍內。
  3. 紋理座標:我們需要儲存 U 和 V 座標;為此,16 位無符號整數提供了足夠的精度,總大小為 4 位元組。我們還告訴 WebGL 規範化這些值到 [0, 1]。

例如,以下頂點

json
{
  "position": [1.0, 2.0, 1.5],
  "normal": [1.0, 0.0, 0.0],
  "texCoord": [0.5, 0.25]
}

將按以下方式儲存在陣列緩衝區中

WebGL array buffer contents

建立陣列緩衝區

首先,我們使用 DataView 從 JSON 資料動態建立陣列緩衝區。請注意使用 true,因為 WebGL 期望我們的資料採用小端位元組序。

js
// Load geometry with fetch() and Response.json()
const response = await fetch("assets/geometry.json");
const vertices = await response.json();

// Create array buffer
const buffer = new ArrayBuffer(20 * vertices.length);
// Fill array buffer
const dv = new DataView(buffer);
vertices.forEach((vertex, i) => {
  dv.setFloat32(20 * i, vertex.position[0], true);
  dv.setFloat32(20 * i + 4, vertex.position[1], true);
  dv.setFloat32(20 * i + 8, vertex.position[2], true);
  dv.setInt8(20 * i + 12, vertex.normal[0] * 0x7f);
  dv.setInt8(20 * i + 13, vertex.normal[1] * 0x7f);
  dv.setInt8(20 * i + 14, vertex.normal[2] * 0x7f);
  dv.setInt8(20 * i + 15, 0);
  dv.setUint16(20 * i + 16, vertex.texCoord[0] * 0xffff, true);
  dv.setUint16(20 * i + 18, vertex.texCoord[1] * 0xffff, true);
});

為了獲得更高的效能,我們也可以在伺服器端進行之前的 JSON 到 ArrayBuffer 轉換,例如使用 Node.js。然後我們可以載入二進位制檔案並將其解釋為陣列緩衝區

js
const response = await fetch("assets/geometry.bin");
const buffer = await response.arrayBuffer();

使用 WebGL 消耗陣列緩衝區

首先,我們建立一個新的頂點緩衝區物件 (VBO) 併為其提供我們的陣列緩衝區

js
// Bind array buffer to a Vertex Buffer Object
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);

然後,我們指定陣列緩衝區的記憶體佈局,透過自己設定索引

js
// Describe the layout of the buffer:
// 1. position, not normalized
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 20, 0);
gl.enableVertexAttribArray(0);
// 2. normal vector, normalized to [-1, 1]
gl.vertexAttribPointer(1, 4, gl.BYTE, true, 20, 12);
gl.enableVertexAttribArray(1);
// 3. texture coordinates, normalized to [0, 1]
gl.vertexAttribPointer(2, 2, gl.UNSIGNED_SHORT, true, 20, 16);
gl.enableVertexAttribArray(2);

// Set the attributes in the vertex shader to the same indices
gl.bindAttribLocation(shaderProgram, 0, "position");
gl.bindAttribLocation(shaderProgram, 1, "normal");
gl.bindAttribLocation(shaderProgram, 2, "texUV");
// Since the attribute indices have changed, we must re-link the shader
// Note that this will reset all uniforms that were previously set.
gl.linkProgram(shaderProgram);

或者我們可以使用顯示卡提供的索引而不是自己設定索引;這避免了著色器程式的重新連結。

js
const locPosition = gl.getAttribLocation(shaderProgram, "position");
gl.vertexAttribPointer(locPosition, 3, gl.FLOAT, false, 20, 0);
gl.enableVertexAttribArray(locPosition);

const locNormal = gl.getAttribLocation(shaderProgram, "normal");
gl.vertexAttribPointer(locNormal, 4, gl.BYTE, true, 20, 12);
gl.enableVertexAttribArray(locNormal);

const locTexUV = gl.getAttribLocation(shaderProgram, "texUV");
gl.vertexAttribPointer(locTexUV, 2, gl.UNSIGNED_SHORT, true, 20, 16);
gl.enableVertexAttribArray(locTexUV);

規範

規範
WebGL 規範
# 5.14.10

瀏覽器相容性

另見