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

浏览器兼容性

另见