OpenGL绘制流程分析

基于Android P版本分析

ARRAY_BUFFER
scss 复制代码
int main()
{
    ..............................
​
    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
        // positions         // colors
         0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  // bottom left
         0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f   // top 
​
    };
​
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);
​
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
​
    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
​
    // 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.
    // glBindVertexArray(0);
​
    // as we only have a single shader, we could also just activate our shader once beforehand if we want to 
    glUseProgram(shaderProgram);
​
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);
​
        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
​
        // render the triangle
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);
​
        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
​
    ........................
}
void glGenBuffers(GLsizei n, GLuint *buffers)

该函数主要是用于生成缓冲区对象的名称buffer Object Name;

  • n:代表了需要生成的buffer object name的个数,一般情况下为1,当n < 0时,会出现INVALID_VALUE 的错误;
  • buffers:用于保存被创建的buffer object name,该name使用数字进行表示;

我们首先先说一下,为什么要使用这个方式,一般情况下,glGenBuffers函数和glBindBuffer函数是配对出现的。

我们首先先理一下CPU和GPU,我们在进行图像呈现的时候,图像是在CPU这边进行加载的,CPU加载成功之后,将数据保存到CPU的内存中,紧接着下一步就是将CPU内存中的数据转移到GPU中,其中包括两种方式:

  • 直接把CPU内存中的数据赋值给GPU中对应的变量;
  • 把CPU中的数据打包,然后在GPU中创建一个buffer object,将CPU中打包的数据传给GPU中的buffer object,然后再在GPU中对这个buffer进行访问和使用;

这两种方式的区别:

  • 直接将CPU的内存数据赋值给GPU变量,这种方式需要进行多次传递,所以从CPU到GPU的传递是耗时又耗资源,而使用buffer object方式,就是一次性把数据全部传给GPU中的buffer object中,然后GPU只需要根据buffer object中数据偏移,就可以将该buffer object的某一块分配给某个变量,另外一块分配给另外一个变量;
  • 比如说一组有可能需要被多次使用,比如顶点颜色在绘制的时候,可能会被多次调用,使用第一种方法,这个顶点颜色的数据必须通过赋值拷贝的方式从CPU中获取,而第二种方法只要保证buffer object没有失效,则GPU就可以直接访问buffer object;
void glBindBuffer(GLenum target, GLuint buffer)

glGenBuffers函数创建了对应的buffer object name,该函数就是用于为该buffer object name创建指定的buffer object。

  • target:指定buffer object的类型,就好像shader分为vertex shader和fragment shader一样,buffer object 也是分类型的, 在 OpenGL ES2.0 中,buffer object 分为 VBO vertex buffer object 和 IBO index buffer object 两种,例如GL_ARRAY_BUFFER(顶点缓冲区)、GL_ELEMENT_ARRAY_BUFFER(顶点索引缓冲区对象)等等,其中GL_ARRAY_BUFFER对应的是VBO,GL_ELEMENT_ARRAY_BUFFER对应的是IBO;
  • buffer:这个参数就是刚刚通过个LG恩Buffers函数得到的buffer object name;

当这个buffer object name没有对应创建和关联一个buffer object,那么该name配对对应的buffer object,新创建的buffer object是一个空间为0,且初始状态为默认值的 buffer,初始状态为 GL_STATIC_DRAW。然后创建和关联完毕之后,也就会把这个 buffer object 当作是当前GPU所使用的VBO或者IBO了;

虽然GPU中可以存放大量的buffer object,但是同一时间一个Thread的一个Context中只能有一个VBO和一个IBO是被使用着的,所以如果想使用某个buffer object,必须先通过glBindBuffer这个Buffer推出来,设置为GPU当前的VBO或者IBO,然后GPU之前使用的VBO或者IBO就不再是处于被使用状态了;

void glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage)

创建了Buffer object之后,就需要给这个buffer object赋予赋予,这个函数就是将CPU端保存的数据传递给GPU侧,保存在指定的buffer object;

  • target:指定了buffer object的类型,可以是 GL_ARRAY_BUFFER 或者 GL_ELEMENT_ARRAY_BUFFER;
  • size:data数据buffer的大小;
  • data:CPU中一块指向保存实际数据的内存;
  • usage:buffer object 的数据存储方式,buffer object的usage初始状态下为GL_STATIC_DRAW,GL_STATIC_DRAW意思就是该buffer object只会被赋值一次,然后在GPU中可能会多次读取调用;
void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data)

这个函数和glBufferData函数类似,只不过是将部分数据传递给buffer object;

  • offset:以buffer object的开始为七点,进行offset个位置的偏移,从这个位置开始,长度为size个单位的这么一块空间,使用data指向的一块CPU中的内存数据进行覆盖;
void glDeleteBuffers(GLsizei n, const GLuint * buffers)

该函数将不需要被使用的buffer对应的buffer object name删除;

  • n:指定需要删除的buffer object个数;
  • buffers:指向的就是buffer 对应的object name;

当buffer object被删除之后,其中的内容也会被删除,名字也会被释放,可以被glGenBuffers函数重新使用,如果被删除的buffer正在处于bind状态,那么就相当于先执行了一次glBindBuffer把对应的binding变成unbind,然后再进行删除;

void glBindAttribLocation(GLuint program, GLuint index, const GLchar *name)

将通用顶点属性索引与命名属性变量相关联,该函数用于将程序指定的程序对象中的用户定义属性变量与通用顶点属性索引相关联;

  • program:指定要在其中建立关联的程序对象的句柄,其实就是保存shader的program;
  • index:指定要绑定的通用顶点属性的索引;
  • name:指定一个以空终止符结尾的字符串,其中包含要绑定索引的顶点着色器属性变量的名称;
GLint glGetAttribLocation(GLuint program, const GLchar*name)

glGetAttribLocation函数用于获取指定 VS 中某个 attribute 的位置。获取到这个位置之后,才可以通过别的函数对这个 attribute 进行操作。

  • program:保存shader的program;
  • name:该参数就是一个字符串,用于保存program对应的vertex shader中的一个attribute的变量名;
void glEnableVertexAttribArray(GLuint index) / void glDisableVertexAttribArray (GLuint index)

在叙述顶点着色器的时候,我们提及到了顶点属性的概念,默认情况下,处于性能考虑,所有顶点着色器的属性变量都是关闭的,意味着数据在着色器端是不可见的,哪怕数据已经上传到GPU,所以该函数用于启用指定属性,才可以在顶点着色器中访问顶点的属性数据;

  • index:指定要使能的通用顶点属性的索引;

对应的enable,也存在disenable;

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

该函数的主要作用就是向VS中传输数据,而这些数据有可能存放在CPU内存中,也有可能存放在刚刚创建好的buffer object中,不管存放在哪里,可以通过该函数对attribute进行赋值,然后被赋值的attribute就代表着若干个顶点的坐标或者颜色等其他信息;

  • index:这个参数代表的就是attribute的location值;
  • size:用于表示一个顶点的所有数据的个数;
  • type:顶点描述数据的类型;
  • normalized:该参数意为意思是如果赋值给 attribute 的值为定点值或者整数值,在传递给 attribute,转化为 float 值的时候,是否需要被归一化,归一化就是带符号的值转化为(-1,1),不带符号的值转化为(0,1);
  • stride:一个顶点占有的总的字节数;
  • pointer:当前指针指向的vertex内部的偏离字节数,可以唯一的标识顶点某个属性的偏移量;
void glVertexAttrib*f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)

除了glVertexAttribPointer函数之后,还有该函数也可以简单的给attribute赋值,每个attribute最多由4个值组成,那么假如 attribute 由 1 个值组成,那么这里使用 glVertexAttrib1f 传入 1 个值,那么正好这个值就赋给了这个 attribute。但是假如 attribute 由 4 个值组成,而使用 glVertexAttrib1f 传入 1 个值,那么其他三个值就使用默认值,第二个和第三个为 0,第四个位 1。

依此类推,如果使用 glVertexAttrib2f 传入 2 个值,那么其他两个值就使用默认值,第三个为 0,第四个位 1。

如果使用带 f 结尾的 API, 比如 glVertexAttrib4f,意思也就是传入的值为 float 类型,如果使用带 v 结尾的 API,比如 glVertexAttrib4fv,和使用 glVertexAttrib4f 唯一的区别也就是传入参数不同,使用 glVertexAttrib4f,需要传入四个值,使用 glVertexAttrib4fv,只需要传入一个包含四个值的指针即可。

GLint glGetUniformLocation(GLuint program, const GLchar *name)

在说 glLinkProgram 的时候,说过 linkprogram 的时候,会给没有指定 index 的 attribute 分配一个 location,也会把所有 shader 中开发者自定义的 active 的 uniform 初始化为 0,并且分配给其一个地址。而每次 relink 之后,uniform 的 location 就会被释放掉,然后再重新分配一个新的 location,当然新的 location 和 旧的 location 也有可能相同。而 glGetUniformLocation 这个 API 就是用于获取 uniform 的 location 的。

  • program:就是包含shader的program object;
  • name:是一个字符串,用于保存program对应的一个uniform变量名,这里不会去检验name的有效性;
void glUniform*iv(GLint location, GLsizei count, const GLint *value)

这个函数是针对uniform变量进行赋值,都是通过location的方式进行赋值;

glUniform*函数一共有3种赋值方式:

  • 第一种是直接以值的形式把数据传递给 Uniform,比如 glUniform1f, glUniform4f,glUniform4i 等;
  • 第二种是以指针形式把数据传给 uniform,比如 glUniform1fv、glUniform4fv、 glUniform4iv 等;
  • 第三种是以指针的形式把数据传给 uniform,比如 glUniformMatrix2fv、 glUniformMatrix4fv;
void glViewport(GLint x, GLint y, GLsizei width, GLsizei height)

该函数就是用设定绘制区域的,绘制的区域不一定要占满这个buffer;

  • x / y:以像素为单位,指定了视口的左下角位置;
  • width:指定了视口矩形的宽度;
  • height:指定了视口矩形的高度;
void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)

该函数就是用于设定一种颜色,作为清理绘制buffer所使用的颜色,该颜色作为底色;

这个函数共有4个输入参数,分别是RGBA四个值,用于确定一种特定的颜色,供清理绘制buffer使用;

void glClear(GLbitfield mask)

该函数用于清理绘制的buffer,因为我们通过egl创建的绘制的buffer,其实就是一块内存,但是这个内存在刚被创建好的时候,并不会被初始化好,里面保存了东西无法确定,所以需要使用该函数进行初始化或者清理;

  • mask:用于指定绘制buffer中的哪一块被清理,因为在绘制buffer中,存在3种buffer:color buffer、depth buffer、stencil buffer。GL_COLOR_BUFFER_BIT mask指定了color buffer;
void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)

当PS结束后,Mask可以限制color、depth、stencil是否可以写入对应的buffer。

比如这个glColorMask API就是用于控制color buffer R\G\B\A通道的写入权限的,如果传入的为GL_TRUE,则说明该通道有写入权限,初始化状态下,默认RGBA四个通道都是可写的,当传入GL_FALSE的时候,所有的color buffers的R通道将都不可写;

无法只控制某个通道的权限,只能一次性控制四个通道;

void glDrawArrays(GLenum mode, GLint first, GLsizei count)

glClear函数本质上也是一个对color buffer的重置,也算是一次绘制;

该函数本质上就是使用传入的顶点信息,在绘制buffer中真正进行绘制,在描述attribute的时候,我们知道了其中包含了顶点的位置、颜色等信息,都已经在GPU侧保存了3个顶点的信息,该函数告知GPU,使用这3个顶点其中的哪几个顶点进行绘制,同时如何进行土元装配;

  • mode:需要渲染的图元类型,包括GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES, GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN,GL_TRIANGLES;

    • GL_POINTS:把每一个顶点作为一个点进行处理,顶点n即定义了点n,共绘制N个点;
    • GL_LINES:连接每两个顶点作为一个独立的线段,顶点2n-1和2n之间共定义了n条线段,总共绘制N/2条线段
    • GL_LINE_STRIP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了线段n,总共绘制n-1条线段
    • GL_LINE_LOOP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点和第一个顶点相连,第n和n+1个顶点定义了线段n,总共绘制n条线段
    • GL_TRIANGLES:把每三个顶点作为一个独立的三角形,顶点3n-2、3n-1和3n定义了第n个三角形,总共绘制N/3个三角形
    • GL_TRIANGLE_STRIP:绘制一组相连的三角形,对于奇数n,顶点n、n+1和n+2定义了第n个三角形;对于偶数n,顶点n+1、n和n+2定义了第n个三角形,总共绘制N-2个三角形
    • GL_TRIANGLE_FAN:绘制一组相连的三角形,三角形是由第一个顶点及其后给定的顶点确定,顶点1、n+1和n+2定义了第n个三角形,总共绘制N-2个三角形
  • first:用于描述从第几个顶点开始绘制;
  • count:需要使用几个顶点作为绘制的点;
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid * indices)

和glDrawArrays功能基本一样,唯一的区别就是选取绘制所使用的顶点的方式不同。假如GPU中一共保存有10个顶点的信息,那么通过glDrawArrays,我们会从这10个顶点中选取一个起点,然后再确定一个长度,所以取到的是连续的几个顶点。

而glDrawElements更加灵活一些,开发者可以通过它指定一些不连续的顶点,所以需要传入一个数组,这个数组保存的就是顶点的index,比如我们可以传入一个1357,也就是选择第一第三第五第七个顶点,第一个绘制顶点为1,依次类推,用于绘制,GPU中其他的顶点,会在这次绘制中就会被忽略掉。

  • mode:和glDrawArrays函数一样,都是用于指定图元装配mode;

  • count:需要使用几个顶点作为绘制的点;

  • type:indices数组的数据类型,即索引值,一般是整型类型;

  • indices:分为两种情况:

    • 假设数据保存在CPU端,那么indices就是一个指向实际数据存放位置的指针或者数据地址;
    • 如果实际数据保存在GPU端,那么indices就传入一个偏移,意思就是从IBO的某一位开始,从之后的那些数值读取count个数值作为indices的值;
相关推荐
xianjian09129 小时前
MySQL 的 INSERT(插入数据)详解
android·数据库·mysql
欧简墨10 小时前
kotlin Android Extensions插件迁移到viewbinding总结
android·trae
尘世中一位迷途小书童11 小时前
前端工程化基石:package.json 40+ 字段逐一拆解
前端·javascript·架构
货拉拉技术11 小时前
优雅解决Android app后台悬浮窗权限问题
android
架构师沉默11 小时前
Gemini 正式登陆香港,不用翻墙!
java·后端·架构
飞Link11 小时前
LangChain Core 架构深度剖析与 LCEL 高阶实战
人工智能·架构·langchain
用户693717500138411 小时前
Android 手机终于能当电脑用了
android·前端
用户51722315748012 小时前
android资源类型与布局资源详细介绍
android
优选资源分享13 小时前
GKD v1.11.6 | 安卓开屏广告跳过工具 可用版
android
robotx13 小时前
安卓zygote启动相关
android