OpenGLES 入门理解

先推荐几篇好的博文:

https://niyaoyao.github.io/2018/05/23/learning_opengl(es)_opengl_model_pipeline_and_practices/

https://blog.csdn.net/lance710825/article/details/78137950/

https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/04%20Hello%20Triangle/

 

这里不详细描述渲染管线的流程,大致写一下: 一堆顶点数据(OpenGL的坐标系),经过系列的变换,最终被GPU输出到FrameBuffer额过程.

一些基础的术语名词理解(个人理解;

这些顶点数据被放在一个数组中; 顶点数据一般是这个顶点的坐标(x,y,z);

顶点属性: 包含了一个顶点数据(顶点坐标)及其他属性, 如: 颜色(RGB),法向量(Normal),纹理坐标(Texture Coordinate)等属性

"顶点属性"意味着GPU中的shader程序每调用一次,顶点缓冲区都会为其提供一个新的顶点数据.OpenGLES 入门理解_第1张图片

上图是一个包含了颜色的顶点属性

//接下来用顶点属性表示要被处理的数据; 可以认为顶点属性就是顶点数据

顶点缓冲区:  这些顶点属性都是要被复制到GPU中的内存(显存)中.

这个内存就被顶点缓冲对象(Vertex Vuffer Object, VBO)管理,我们可以认为VBO 就是一块内存区域,里面存储了一堆顶点属性;


//GLuint  无符号四字节整型,包含数值从0 到 4,294,967,295

GLuint VBO;
//生成一个VBO对象, 
glGenBuffers(1, &VBO);

glGenBuffers()

意思是该函数用来生成缓冲区对象的名称。

函数原型:void glGenBuffers(GLsizei n,GLuint * buffers);

glGenBuffers()函数 仅仅是生成一个缓冲对象的名称,类似c语言中的一个指针变量,仅仅是个缓冲对象,还不是一个顶点数组缓存. 

因为这时候根本没有进行内存分配, 可以理解只是声明了一个变量, 我们准备用这个变量来引用准备分配的内存.


GLuint vbo;
glGenBuffers(1,&vbo);
GLuint vbo[3];
glGenBuffers(3,vbo);

个人理解如下,可以声明一个GLuint变量,然后使用glGenBuffers后,它就会把缓冲对象保存在vbo里,当然也可以声明一个数组类型,那么创建的3个缓冲对象的名称会依次保存在数组里。

声明一个变量时,使用&VBO,声明一个数组类型,直接使用VBO; C语言, 数组名=数组首元素指针.

已经声明了缓冲对象, 那这个缓冲对象是什么类型,需要用到glBindBuffer();

函数原型:

void glBindBuffer(GLenum target,GLuint buffer);

第一个: 缓冲对象类型

第二个: 要绑定的缓冲对象的名称, 即我们在上一个函数里生成的名称.

按照我的理解 : 确定了VBO缓冲区类型.

在OpenGL红包书中给出了一个恰当的比喻:绑定对象的过程就像设置铁路的道岔开关,每一个缓冲类型中的各个对象就像不同的轨道一样,我们将开关设置为其中一个状态,那么之后的列车都会驶入这条轨道。

缓冲区的类型:

GL_ARRAY_BUFFER:数组缓冲区存储颜色、位置、纹理坐标等顶点属性,或者其它自定义属性
GL_COPY_READ_BUFFER:缓冲区用作通过glCopyBufferSubData进行复制的数据源
GL_COPY_WRITE_BUFFER:缓冲区用作通过glCopyBufferSubData进行复制的目标
GL_ELEMENT_ARRAY_BUFFER:索引数组缓冲区用于保存glDrawElements、glDrawRangeElements和glDrawElementsInstanced的索引
GL_PIXEL_PACK_BUFFER:glReadPixels之类像素包装操作的目标缓冲区
GL_PIXEL_UNPACK_BUFFER:glTexImageXD、glTexSubImageXD之类的纹理更新函数的源缓冲区
GL_TEXTURE_BUFFER:着色器可以通过纹理单元拾取来访问的缓冲区
GL_TRANSFORM_FEEDBACK_BUFFER:变换反馈顶点着色器写入的缓冲区
GL_UNIFORM_BUFFER:着色器访问的uniform值

 

绑定完缓冲区之后, 要把数据传递到缓冲区:

函数原型:

glBufferData(GLenum target, GLsizeiptrARB size, const void *data, GLenum usage);

glBindBuffer(GL_ARRAY_BUFFER, VBO);  //VBO变成了一个顶点缓冲类型
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

以上 按照我的理解如下,不一定正确,但是适合我理解:

声明了一个变量或者一个数组, 准备用这个变量(数组)去管理一块(多块)内存(缓存)区域,这(多)块内存区域的类型 需要我们设置, 确保这块内存能干什么;

 

由于顶点属性(数据)有很多, 所以用一块内存来存储,这块内存由VBO管理, 可以看成VBO就是这块内存;

那么VBO也可以是多个,使用VAO(Vertex Array Object)进行管理,VAO存储这attributePointer, 即顶点属性的指针;

好处是: 当配置顶点属性指针时, 只要将那些调用执行一次,之后再绘制物体的时候只需要绑定响应的VAO;

这使得不同的顶点数据和属性配置之间切换变的非常简单,只需要绑定不同的VAO就行了,之前设置的状态都存储在VAO中了;

VAO 实际是存储顶点属性指针的数组;

 EBO(Element Buffer Object,也叫IBO: index Buffer Object) 索引缓冲区对象,这个缓冲区主要用来存储顶点的索引信息

画一个四边形时, 可以看成是两个三角形组成. 那么有两个点是重复的. 这是可以省去的..

 使用索引, 我们只需要基础的四个点的数据,根据索引从对应的数据中取得点 .

与VBO类似,声明完EBO的变量之后,先生成EBO的缓冲区,然后绑定,最后设置缓冲区数据,缓冲区类型是GL_ELELMENT_ARRAY_BUFFER.

glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。当目标是GL_ELEMENT_ARRAY_BUFFER的时候,VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了.

所以  配置EBO时, VAO自动将EBO存储了.在内存中,EBO 位于 VAO 最后的 element buffer objectOpenGLES 入门理解_第2张图片

 

OpenGLES 入门理解_第3张图片

完整的缓冲绑定代码. 在绑定缓冲区之前, 必须先生成VVAO.之后进行VBO和VAO的生成,绑定和设置. 这是设置顶点属性的指针. 

其中glVertexAttribPointer  函数用于向OpenGL 解释如何解析顶点数据.

顶点数据会被解析为下面的样子:

OpenGLES 入门理解_第4张图片

  • 位置数据被储存为32-bit(4字节)浮点值。
  • 每个位置包含3个这样的值。
  • 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列。
  • 数据中第一个值在缓冲开始的位置。

 

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

 glVertexAttribPointer 函数中:

  • 第一个参数明确了 Position 的 location。在之前编写的 Vertex Shader 中,我们已经设置了固定的 location layout(location = 0),即顶点属性的位置值为 0。
  • 第二个参数为顶点属性的 size。顶点属性的顶点坐标属性是个三维向量 vec3 类型的数据,所以 size 大小为 3。
  • 第三个参数明确属性数据类型,这里是 GL_FLOAT,即 float 型。
  • 第四个参数定义是否希望数据被标准化(Normalize)。
  • 第五个参数是顶点属性的步长(Stride),其中 Position 有是 vec3 类型的变量,所以步长为 3 * sizeof(float)
  • 第六个参数表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是 0。

生成(Generate),绑定顶点数据有关buffer的过程.

必须先生成VAO,glGenVertextArrays

绑定VAO,glBindVertexArray

生成VBO, glGenBuffers

绑定VBO, glBindBuffer

把顶点缓冲对象赋值到缓冲中区供OpenGL使用, glBufferData

生成EBO,glGenBuffers

绑定EBO, glBindBuffer

把元素缓冲对象赋值到缓冲中供OpenGL 使用, 管理glBufferData;

设定顶点属性,glVertexAttributePointer

之后可以进行绘制;

 

 

 

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    // 2. Make the context current
    //切换上下文
    [[self openGLContext] makeCurrentContext];
    
    // 3. Define and compile vertex and fragment shaders
    
    //Shader 程序文件本质也是向应用程序提供常量字符串
    GLuint  vs;
    GLuint  fs;
    const char *vss = "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    "}\0";
    
    const char *fss = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n\0";
    
    
    //创建一个顶点着色器
    vs = glCreateShader(GL_VERTEX_SHADER);
    //把着色器源码附加到着色器对象上
    //第一个参数: 要编译的着色器对象
    //第二个: 传递的源码字符串数量
    //第三个: 顶点着色器真正的源码  &vss
    glShaderSource(vs, 1, &vss, NULL);
    glCompileShader(vs);
    
    //同上 编译fragment着色器
    fs = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fs, 1, &fss, NULL);
    glCompileShader(fs);
    printf("vs: %i, fs: %i\n",vs,fs);
    
    int success;
    char infoLog[512];
    //检查编译是否报错c
    glGetShaderiv(vs, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vs, 512, NULL, infoLog);
        printf("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n%s\n", infoLog);
    }
    
    glGetShaderiv(fs, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fs, 512, NULL, infoLog);
        printf("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n%s\n", infoLog);
    }
    
    // 4. Attach the shaders
    //创建shader Program
    shaderProgram = glCreateProgram();
    //俯角shader到program
    glAttachShader(shaderProgram, vs);
    glAttachShader(shaderProgram, fs);
    //链接program
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        printf("ERROR::SHADER::PROGRAM::LINKING_FAILED\n%s\n", infoLog);
    }
    //删除shader,已经链接上了,不需要了
    glDeleteShader(vs);
    glDeleteShader(fs);
    printf("positionUniform: %i, colourAttribute: %i, positionAttribute: %i\n",positionUniform,colourAttribute,positionAttribute);
    // There is no space (or other values) between each set of 3 values. The values are tightly packed in the array.
    //正方形的四个顶点
    float vertices[] = {
        0.5f,  0.5f, 0.0f,  // top right
        0.5f, -0.5f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  // bottom left
        -0.5f,  0.5f, 0.0f   // top left
    };
    //三角形的顶点索引
    unsigned int indices[] = {  // note that we start from 0!
        0, 1, 3,  // first Triangle
        1, 2, 3   // second Triangle
    };
    unsigned int VBO, VAO, EBO;
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    
    // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
    //解绑当前活动内存设置为空, 因为glVertexAttribPointer 已经将VBO作为顶点属性绑定到缓冲区对象
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    // remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound.
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    
    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    //解绑VAO
    glBindVertexArray(0);
    // Drawing code here.
    
    //glViewport 函数,对视口在窗体中的 x 坐标、y 坐标、宽度、高度进行设置,最终得到在窗体中的坐标
    glViewport(10, 10, 100, 100);
    //设置背景颜色
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    //清空屏幕
    glClear(GL_COLOR_BUFFER_BIT);
    
    // draw our first triangle
//    glUseProgram(shaderProgram);
//    glBindVertexArray(VAO);
//    glDrawArrays(GL_TRIANGLES, 0, 3);
//    glBindVertexArray(0);
    //使用创建的shaderProgram,已经附加了shaderProgram
    glUseProgram(shaderProgram);
    //绑定VAO
    glBindVertexArray(VAO);
    //(使用了EBO)绘制图形
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    
    [[self openGLContext] flushBuffer];
}

代码来源就是上面贴的第一篇博客.

博客写的有点乱,因为这是自己在学OpenGL时候的记录,方便自己回顾. 

你可能感兴趣的:(OpenGL,ES)