Android OpenGL基础1——常用概念及方法解释

OpenGL 对象

在顶点数据定义好了之后,通常这些属性会作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于存储顶点数据,同时还要告诉OpenGL如何去解释内存中的这些数据,并且指定如何发送到显卡。顶点着色器接下来会处理内存中指定数量的顶点。通常会采用VBO对象来管理这个GPU上分配的内存。

VBO对象

VBO,即顶点缓冲对象(Vertex Buffer Object),主要作用就是可以一次性发送大批顶点数据到显卡上,而不是每个顶点发送一次。原因是CPU传送数据给GPU其实是比较耗时的,所以尽可能的一次性把需要的顶点数据全部传给GPU,这样顶点着色器几乎能立即访问到顶点,有助于加快顶点着色器效率。

系列文章

创建步骤

  • 生成一个(或多个)缓冲类型的id

    1
    2
    3
    4
    5
    6
    7
    // 生成一个vbo id
    unsigned int vbo;
    glGenBuffers(1, &vbo);

    // 或者 生成3个 vbo id
    unsigned int vbos[3];
    glGenBuffers(3, &vbos);

    glGenBuffers(GLsizei n, GLuint buffers),生成缓冲对象名字,n为缓冲对象名字个数, buffers为存放名字的数组,*n和数组长度需要一直

  • 将缓冲对象id绑定给指定的缓冲类型

    1
    glBindBuffers(GL_ARRAY_BUFFER, vbo)

缓冲类型通常有:GL_ARRAY_BUFFER, GL_COPY_READ_BUFFER,
GL_COPY_WRITE_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER, GL_TEXTURE_BUFFER,
GL_TRANSFORM_FEEDBACK_BUFFER, or GL_UNIFORM_BUFFER.

注意:OpenGL中存在多种不同类型的缓冲对象(如上所述),所以只要缓冲对象类型不同,OpenGL是允许我们同时绑定多个缓冲的。

小结

创建VBO对称步骤:

  1. 生成VBO对象id(调用glGenBuffers);
  2. 为生成的id指定缓冲类型(调用glBindBuffers);

赋值操作

VBO 对象的赋值主要利用glBufferData函数来进行:

1
2
3
4
5
6
7
/**
* @param target 指定VBO的缓冲类型
* @param size 表示缓冲数据的大小
* @param data 实际缓冲的数据
* @param usage 表示数据的在GL 的实现中是如何被访问的
*/
void glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage);

上面的usage参数,常用的有如下几个值:

  • STREAM,modified once and used at most a few times;
  • STATIC,modified once and used many times;
  • DYNAMIC,modified repeatedly and used many times;

示例:

1
2
3
4
5
6
7
8
9
// 定义顶点数据
float vertices[] = {
// 位置 // 颜色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 顶部
};
// 调用glBufferData 来完成 VBO对象 的赋值
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

支持数据就已经推送到GPU中去了,一个VBO过程就结束了。

数据的处理

上面通过一些列的接口调用,最终完成了想GPU数据的推送,那么GL是怎么告诉GPU如何处理这些数据的呢,毕竟上面的vertices数组描述了顶点数据两种类型的信息:位置和颜色。要处理上面的数据(vertices),需要如下代码:

1
2
3
4
5
6
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
  • glEnableVertexAttribArray,开启对应位置的顶点属性;
  • glDisableVertexAttribArray,关闭随影位置的顶点属性;
  • glVertexAttribPointer,说明顶点属性数据的解析规则;

OpenGL中,主要通过glVertexAttribPointer这个函数来说明该如何解析传递给GPU的数据的。参数说明:

1
2
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
const GLvoid * pointer);
  • index,指定哪个顶点属性奖杯修改,通常对应如顶点Shader中layout(position = 0)这个position;
  • size,表示顶点属性指的大小,上面vertices位置和颜色都是有三个float型数据组成的,所以是3;
  • type,指的是数据的类型,上面vertices对应的时GL_FLOAT;
  • normalized,是否希望数据标准化。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE;
  • stride,表示一条完整顶点属性(包括location和color)的长度(这里需要6个数来表示),所以这里是6;
  • pointer,表示偏移量,即在一段数据中,指定的数据偏移多少位置开始。在这里,坐标数据都是每段数据的起始位置,所以偏移量是0,而颜色数据在坐标数据之后,坐标数据有3个分量,所以每个颜色数据偏移三个float字节开始算;

VAO对象

VAO,即顶点数组对象(Vertex Array Object),VAO作用就是顶点属性调用都会存储在VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。简单来说有点类似复用的概念。 那么VAO到底复用了哪些过程呢,先看物体绘制的一般过程:

1
2
3
4
5
6
7
8
9
10
// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();

如果使用VAO,任何绑定VAO后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。

创建及使用

生成一个id并绑定,然后进行VBO的复制操作,再进行顶点指针的设置操作,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义一个 id,用来存储生成的VAO对象名称
GLuint m_SkyBoxVboId;
// 生成一个VAO对象,并把这个名称存储到上面定义的id中
glGenVertexArrays(1, &m_SkyBoxVaoId);
// 1. 绑定VAO对象
glBindVertexArray(m_SkyBoxVboId);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 2. 把顶点数据复制到缓冲数据中供OpenGL使用
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(m_SkyBoxVboId);
someOpenGLFunctionThatDrawsOurTriangle();

// 当不在使用VAO时,解绑
glBindVertexArray(0);

Sample对象

Sample对象,即采样器对象,它是GLSL有一个供纹理对象使用的内建数据结构,在Shader定义好Sample对象后,后面这个对象就可以接收外面传递进来的纹理对象的值了。

根据纹理的类型,Sample对象也有相对应的类型:

  • 1D->sample1D;
  • 2D->sample2D;
  • 3D->sample3D;

数学基础

要学习好OpenGL,就必须对基本的数学知识有所了解,这里先列出几个点:

  • 向量
  • 矩阵

参考文章

  1. Learn OpenGL中文网站