glVertexAttribPointer

官方 glVertexAttribPointer 函数文档

glVertexAttribPointer 是将 VBO 中的原始数据与顶点着色器(Vertex Shader)中的属性(layout(location = ...))关联起来的关键桥梁。当你的顶点数据变得复杂时(比如同时包含位置、颜色、纹理坐标),理解它的每个参数就变得至关重要。

我们先用一个包含位置(Position)纹理坐标(Texture Coordinate)的顶点数组作为例子,然后为你整理一份清晰的笔记。


示例场景:渲染一个带纹理的正方形

我们要画一个由两个三角形组成的正方形。每个顶点都需要两个信息:

  1. 它在屏幕上的位置 (x, y)。
  2. 它对应纹理图片的哪个坐标 (s, t)。

1. 准备顶点数据 (在 CPU 端)

我们把位置和纹理坐标交错(Interleaved)存放在一个数组里。这样做通常性能更好,因为 GPU 在读取一个顶点的数据时,所有相关属性都在内存中相邻的位置。

1
2
3
4
5
6
7
float vertices[] = {
// ---- 位置 ---- --- 纹理坐标 ---
-0.5f, -0.5f, 0.0f, 0.0f, // 顶点0: 左下
0.5f, -0.5f, 1.0f, 0.0f, // 顶点1: 右下
0.5f, 0.5f, 1.0f, 1.0f, // 顶点2: 右上
-0.5f, 0.5f, 0.0f, 1.0f // 顶点3: 左上
};
  • 这个正方形有4个顶点。
  • 每个顶点有 2个float (位置) + 2个float (纹理坐标) = 4个float
  • 整个数组有 4个顶点 * 4个float/顶点 = 16个float

2. 在顶点着色器中定义输入

我们的顶点着色器 (vertex.shader) 需要接收这两种数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#version 330 core

// 属性槽 0: 接收位置数据
layout(location = 0) in vec2 aPos;

// 属性槽 1: 接收纹理坐标数据
layout(location = 1) in vec2 aTexCoord;

// 输出纹理坐标给片段着色器
out vec2 TexCoord;

void main()
{
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
TexCoord = aTexCoord;
}

3. 使用 glVertexAttribPointer 进行解析

现在,我们已经用 glBufferDatavertices 数组发送到了 GPU 的 VBO 中。GPU 看到的是一长串的、没有结构的字节流。glVertexAttribPointer 的任务就是告诉 GPU 如何去解读这串字节

我们需要调用两次 glVertexAttribPointer,一次为位置属性,一次为纹理坐标属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 假设VBO和VAO已经绑定好了 (glBindBuffer, glBindVertexArray)

// --- 告诉GPU如何解析“位置”属性 (location = 0) ---
glVertexAttribPointer(
0, // 属性索引(location): 对应 layout(location = 0) in vec2 aPos;
2, // 大小(size): 每个顶点的位置属性由 2 个分量组成 (x, y)
GL_FLOAT, // 类型(type): 这些分量是 float 类型
GL_FALSE, // 是否标准化(normalized): 不用,保持原始浮点数值
sizeof(float) * 4, // 步长(stride): 下一个顶点的位置数据在多少字节之后?
// 一个完整的顶点(位置+纹理)包含4个float,所以步长是 4 * sizeof(float)
(const void*)0 // 偏移量(pointer/offset): 位置数据在一个顶点的开头,所以偏移量是 0
);
glEnableVertexAttribArray(0); // 启用 location = 0 这个属性槽


// --- 告诉GPU如何解析“纹理坐标”属性 (location = 1) ---
glVertexAttribPointer(
1, // 属性索引(location): 对应 layout(location = 1) in vec2 aTexCoord;
2, // 大小(size): 每个顶点的纹理坐标属性由 2 个分量组成 (s, t)
GL_FLOAT, // 类型(type): 这些分量是 float 类型
GL_FALSE, // 是否标准化(normalized): 不用
sizeof(float) * 4, // 步长(stride): 同样,下一个顶点的纹理坐标数据也在 4 * sizeof(float) 字节之后
(const void*)(sizeof(float) * 2) // 偏移量(pointer/offset): 纹理坐标数据在一个顶点的什么位置?
// 它在位置数据(2个float)之后,所以偏移量是 2 * sizeof(float)
);
glEnableVertexAttribArray(1); // 启用 location = 1 这个属性槽

可视化解读:


学习笔记:glVertexAttribPointer 函数

1. 核心作用

glVertexAttribPointer 的核心作用是“定义顶点数据在内存中的布局”。它告诉 OpenGL 如何从当前绑定的 GL_ARRAY_BUFFER(VBO)中提取数据,并将其发送到顶点着色器中特定 locationin 变量。

这个函数的所有设置都会被记录在当前绑定的顶点数组对象(VAO)中。

2. 函数原型

1
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer);

3. 参数详解

  • GLuint index

    • 含义: 顶点属性的位置索引(Location Index)
    • 作用: 指定要配置哪个顶点属性。这个值必须与顶点着色器中的 layout(location = ...) 相匹配。
    • 示例: 0 对应 layout(location = 0) in vec3 aPos;
  • GLint size

    • 含义: 每个顶点该属性的分量数量
    • 作用: 指定该属性由几个值组成。例如,一个 2D 位置是2个,3D 位置是3个,RGB 颜色是3个。这个值必须是 1, 2, 3 或 4。
    • 示例: 2 表示位置由 xy 两个分量组成。
  • GLenum type

    • 含义: 属性分量的数据类型
    • 作用: 指定数组中每个分量是什么类型。
    • 示例: GL_FLOAT, GL_INT, GL_UNSIGNED_BYTE 等。最常用的是 GL_FLOAT
  • GLboolean normalized

    • 含义: 是否需要将数据标准化
    • 作用: 如果设为 GL_TRUE,非浮点类型的数据(如 GL_INT, GL_UNSIGNED_BYTE)会被映射到 [0, 1](无符号)或 [-1, 1](有符号)的范围内。对于浮点数,此参数无效。
    • 示例: 通常设为 GL_FALSE。对于用 0-255unsigned byte 表示的颜色,可以设为 GL_TRUE,OpenGL 会自动将其转换为 0.0-1.0 的浮点颜色。
  • GLsizei stride

    • 含义: 步长,即两个连续顶点之间相隔的字节数。
    • 作用: 告诉 OpenGL 在读取完一个顶点的这个属性后,要跳过多少字节才能找到下一个顶点相同属性
    • 计算: 一个完整顶点占用的总字节数。在我们的例子中,一个顶点是 (pos.x, pos.y, tex.s, tex.t),共 4 个 float,所以步长是 4 * sizeof(float)
    • 特殊值: 如果设为 0,表示数据是紧密排列的(Tightly Packed),OpenGL 会自动计算步长(size * sizeof(type))。这只在 VBO 中只有一种属性时才适用。
  • const void* pointer

    • 含义: 偏移量,即在一个顶点的数据块中,该属性从何处开始
    • 作用: 指定属性在顶点数据块内的起始位置的字节偏移。
    • 计算: 它是一个从顶点数据块开始位置的字节偏移量。在我们的例子中,位置属性从 0 开始,纹理坐标属性在位置属性(2个float)之后,所以偏移量是 2 * sizeof(float)
    • 注意: 参数类型是 void*,但它代表的是字节偏移量,不是一个真正的指针。需要进行类型转换,如 (const void*)0

4. 不要忘记 glEnableVertexAttribArray

调用 glVertexAttribPointer 只是配置了如何解析数据,但属性默认是禁用的。必须调用 glEnableVertexAttribArray(index)启用该属性槽,否则数据不会被发送到顶点着色器。