openGL之glsl入门5--缓冲对象vbo、vao及ebo

    大家可以看到,前面几个章节的例子都是平面的,顶点着色器顶点数据只用了xy分量,z分量完全没用,之所以没用立体的例子,绘制立体图形,需配合矩阵旋转来用,否则画出来了也看不到效果,立体图形相对复杂一些,最好能配合缓冲对象来使用,效率更高,代码也会简单一些。这一章通过2个例子和一段用c语言解释vaovbo的方式向大家讲解缓冲对象的概念。

1. 红宝书第一个例子

    基本概念:

1. vaovertex-array object)顶点数组对象,用来管理vbo

2. vbovertex buffer object)顶点缓冲对象,用来缓存用户传入的顶点数据。

3. eboelement buffer object)索引缓冲对象,用来存放顶点索引数据。

    这里的object指的都是GPU中的一块内存,每个内存对象都有不同的作用,但创建、绑定、数据传送等方式都比较类似,通过不同的类型加以区分,掌握了一种,其他的就很好理解。

    vboGPU中的一块buffer,用户可以向这个buffer中写入数据,前面几章的程序没用vbo,都是在调用glDrawArrays时才把顶点数据或颜色数据传送到GPU,使用了vbo,可以把数据传送和绘制操作分开,可以在glDraw 之前把数据传送给GPU如游戏场景,使用了大量的顶点数据,但在游戏过程中,顶点数据只需要做坐标变换,而不用重新传送,如果不使用vbo,效率将非常低,每帧数据都需重新传送数据给GPU

    vao并不是必须的,vbo可以独立使用,vbo值缓存了数据,而数据的使用 方式(glVertexAttribPointer 指定的数据宽度等信息)并没有缓存,当切换vbo时(有多个vbo时,通过glBindBuffer切换 ),数据使用方式信息就丢失了,vao则可以弥补这一点,可以额外记录数据的使用方式。

    ebo也不是必须的,如果使用ebo,绘制过程将更清晰简单,ebo需配合vbo使用,索引必须指定索引的对象。

    理解缓冲对象,可以用红宝书第一个例子,虽然第一个例子中的vao完全可以去掉,下面的例子是根据红宝书第一个例子稍作修改,主要改动:

1. LoadShaders copy到代码中,方便大家编译。

2. vao进行了分组,原代码中vao完全可以删除,反而会影响程序的理解(可有可无的东西放代码里有什么特殊用途)。

3. display的时候进行了vao切换,更能体现出vao的作用。

代码如下:

#include <iostream> 
using namespace std; 
#include <GL/glew.h>
#include <GL/glut.h>
typedef struct {
        GLenum       type;
        const char*  filename;
        GLuint       shader;
} ShaderInfo;
static const GLchar* ReadShader( const char* filename )
{
        FILE* infile;
        fopen_s( &infile, filename, "rb" );
        if ( !infile )
    {
                std::cerr << "Unable to open file '" << filename << "'" << std::endl;
                return NULL;
        }
        fseek( infile, 0, SEEK_END );
        int len = ftell( infile );
        fseek( infile, 0, SEEK_SET );
        GLchar* source = new GLchar[len+1];
        fread( source, 1, len, infile );
        fclose( infile );
        source[len] = 0;
        return const_cast<const GLchar*>(source);
}
GLuint LoadShaders( ShaderInfo* shaders )
{
        if ( shaders == NULL ) { return 0; }
        GLuint program = glCreateProgram();
        ShaderInfo* entry = shaders;
        while ( entry->type != GL_NONE )
                {
                GLuint shader = glCreateShader( entry->type );
                entry->shader = shader;
                const GLchar* source = ReadShader( entry->filename );
                if ( source == NULL ) {
                        for ( entry = shaders; entry->type != GL_NONE; ++entry ) {
                                glDeleteShader( entry->shader );
                                entry->shader = 0;
                        }
                        return 0;
                }
                glShaderSource( shader, 1, &source, NULL );
                delete [] source;
                glCompileShader( shader );
                GLint compiled;
                glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled );
                if ( !compiled )
        {
                        GLsizei len;
                        glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &len );
                        GLchar* log = new GLchar[len+1];
                        glGetShaderInfoLog( shader, len, &len, log );
                        std::cerr << "Shader compilation failed: " << log << std::endl;
                        delete [] log;
                        return 0;
                }
                glAttachShader( program, shader );
                ++entry;
        }
        glLinkProgram( program );
        GLint linked;
        glGetProgramiv( program, GL_LINK_STATUS, &linked );
        if ( !linked )
    {
                GLsizei len;
                glGetProgramiv( program, GL_INFO_LOG_LENGTH, &len );
                GLchar* log = new GLchar[len+1];
                glGetProgramInfoLog( program, len, &len, log );
                std::cerr << "Shader linking failed: " << log << std::endl;
                delete [] log;
                for ( entry = shaders; entry->type != GL_NONE; ++entry )
        {
                        glDeleteShader( entry->shader );
                        entry->shader = 0;
                }
                return 0;
        }
        return program;
}
enum VAO_IDs { Triangles,Triangles1, NumVAOs }; 
enum Buffer_IDs { ArrayBuffer,ArrayBuffer1, NumBuffers }; 
enum Attrib_IDs { vPosition = 1 };
 
GLuint  VAOs[NumVAOs]; 
GLuint  Buffers[NumBuffers]; 
const GLuint NumVertices = 6;
void init(void)
{ 
    ShaderInfo  shaders[] =
    { 
                { GL_VERTEX_SHADER, "triangles.vert" }, 
                { GL_FRAGMENT_SHADER, "triangles.frag" }, 
                { GL_NONE, NULL } 
        }; 
        GLuint program = LoadShaders(shaders); 
        glUseProgram(program); 
   
        glGenVertexArrays(NumVAOs, VAOs); 
        glBindVertexArray(VAOs[Triangles]); 
        printf("Triangles:%d NumVAOs:%d id:%d\n",Triangles,NumVAOs,VAOs[0]);
        GLfloat  vertices[NumVertices][2] =
        { 
                { -0.90, -0.90 },  // Triangle 1 
                {  0.85, -0.90 }, 
                { -0.90,  0.85 }, 
                {  0.90, -0.85 },  // Triangle 2 
                {  0.90,  0.90 }, 
                { -0.85,  0.90 } 
        }; 
        glGenVertexArrays(NumVAOs, VAOs);
        glGenBuffers(NumBuffers, Buffers);
   
        glBindVertexArray(VAOs[Triangles]); 
        glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); 
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices)/2,  vertices, GL_STATIC_DRAW); 
        glVertexAttribPointer(vPosition, 2, GL_FLOAT,  GL_FALSE, 0, 0); 
        glEnableVertexAttribArray(vPosition); 
 
        glBindVertexArray(VAOs[Triangles1]);  
        glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer1]); 
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices)/2,  &vertices[3], GL_STATIC_DRAW); 
        glVertexAttribPointer(vPosition, 2, GL_FLOAT,  GL_FALSE, 0, 0); 
        glEnableVertexAttribArray(vPosition); 
} 
 
void display(void)
{ 
    static int i = 0;
   
        glClear(GL_COLOR_BUFFER_BIT); 
    if(i++%2 == 0)
    {
        glBindVertexArray(VAOs[Triangles]); 
        glDrawArrays(GL_TRIANGLES, 0, NumVertices/2); 
    }
    else
    {
        glBindVertexArray(VAOs[Triangles1]); 
        glDrawArrays(GL_TRIANGLES, 0, NumVertices/2); 
    }
   
    printf("display i:%d\n",i);
        glFlush(); 
} 
int main(int argc, char** argv) { 
        glutInit(&argc, argv); 
        glutInitDisplayMode(GLUT_RGBA); 
        glutInitWindowSize(512, 512); 
        glutCreateWindow(argv[0]); 
        if (glewInit())
    { 
                cerr << "Unable to initialize GLEW ... exiting" << endl; exit(EXIT_FAILURE); 
        } 
        init(); 
        glutDisplayFunc(display); 
        glutMainLoop(); 
}

    shader程序源码,需把下面的shader代码保存为triangles.verttriangles.frag文件

#version 330 core
 
layout (location = 0) in vec4 vPosition; 
layout (location = 1) in vec4 vPosition1; 
void  main() 
{ 
    gl_Position = vPosition1; 
}
 
#version 330 core
 
out vec4 fColor;
void  main() 
{ 
    fColor = vec4(0.0, 0.5, 0.5, 0.0); 
}  
     程序的效果如下(改变窗口大小时触发重绘,可以看到2 个三角形切换):

  

    API可以自己看红宝书,这里把程序流程讲解一下:

1. 先创建2vao对象用来管理vbo对象,对应的vbo也创建了2个。先通过glBindVertexArray切换vao,让后续的操作都作用到对应的vao上,设置完vbo相关参数,此时vbo的设置都关联到vao上了。

2. 可以通过改变窗口大小来触发重绘,display函数切换着画三角形,只调用了2句话,即切换vaoglDrawArrays,如不使用vao,那么glDrawArrays 之前init函数里初始化vbo的几句话都需要带上才能达成同样的效果。

 注意:

1. 使用vbo的时候,glVertexAttribPointer的参数含义有变化,参数Pointer 可以传NULL第二章有说明。

2. glBufferData的时候应该把数据传送到了GPU,而不像未使用vbo,调用glDrawArrays的时候才传送数据,程序用的是局部变量就可以说明这一点(display时数据指针已经失效)。 

2. c代码解释vbovao

    最开始接触这个概念的时候,也是半天没理解,对vao idvbo idvPosition位置的对应关系不是很理解,看了一些资料,都是强调“状态 ”这两个字,个人对状态的理解就是全局量保存的当前信息。为方便理解,下面是按个人理解的情况,整理的实现模型,模拟用户程序、openGL apiGPU程序交互过程, 大家可以参考一下。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* -------------------以下部分是对api部分的模拟-----------------*/
typedef int GLuint;
typedef float GLfloat;
#define MAX_VAO 16
#define MAX_VBO 16
/* 为了编译,随便定义的*/
#define GL_ARRAY_BUFFER    1
#define GL_ARRAY_BUFFER    1
#define GL_STATIC_DRAW     1
#define GL_FLOAT           1
#define GL_FALSE           1
#define GL_ARRAY_BUFFER    1
#define GL_ARRAY_BUFFER    1
#define GL_STATIC_DRAW     1
#define GL_FLOAT           1
#define GL_FALSE           1
#define GL_TRIANGLES       1
#define GL_TRIANGLES       1
/* vbo对象结构,存储数据指针及大小*/
typedef struct
{
        int enable;
        char * vData;
        GLuint vDataSize;
}VBO;
/* vao对象结构,存储了vbo id及相关的信息*/
typedef struct
{
        int enable;
        GLuint vType;
        GLuint vboId;
        GLuint vPosition;
        GLuint vSize;
        GLuint vNormalized;
        GLuint vStride;
}VAO;
VAO gVaos[MAX_VAO];
VBO gVbos[MAX_VBO];
VAO * gCurVao = NULL;
VBO * gCurVbo = NULL;
/* 从vao数组中返回未使用的vao数组下标*/
void glGenVertexArrays(int numVao,int * array)
{
        int cnt = 0;
        for(int i = 0;i<MAX_VAO && cnt < numVao;i++)
        {
                if( !gVaos[i].enable)
                {
                        array[cnt] = i;
                        cnt++;
                        gVaos[i].enable = 1;
                }
        }
}
/* 把指定的vao_id设置为当前全局vao */
void glBindVertexArray(int vao_id)
{
        gCurVao = &gVaos[vao_id];
}
/* 从vbo数组中返回未使用的vbo数组下标*/
void glGenBuffers(int numVbo,int * array)
{
        int cnt = 0;
        for(int i = 0;i<MAX_VBO && cnt < numVbo;i++)
        {
                if( !gVbos[i].enable)
                {
                        array[cnt] = i;
                        cnt++;
                        gVbos[i].enable = 1;
                }
        }
}
/* 把指定的vbo_id设置为当前全局bao */
void glBindBuffer(int vtype,int vbo_id)
{
        if( gCurVao)
        {
                gCurVao->vType = vtype;
                gCurVao->vboId = vbo_id; /* 使用了vao,则把vbo_id等信息存入当前的vao中*/
        }
        gCurVbo = &gVbos[vbo_id];
}
/* 数据存储到当前的vbo中*/
void glBufferData(int vtype, int size, const void * data, int usage)
{
        gCurVbo->vDataSize = size;
        if( gCurVbo->vData == NULL)
        {
                gCurVbo->vData = (char *)malloc(size);
                memcpy(gCurVbo->vData,data,size);
        }
}
/* 告知shader数据怎么使用,如有vao,则把信息存下来*/
void glVertexAttribPointer(int vpos,int size, int type, int normalized,int stride,const void * pointer)
{
        if( gCurVao)
        {
                gCurVao->vPosition = vpos;      /* 存储的信息也包括顶点数据的位置索引*/
                gCurVao->vSize = size;
                gCurVao->vNormalized = normalized;
                gCurVao->vStride = stride;
        }
}
void glEnableVertexAttribArray(int vpos)
{
}
void glDrawArrays(int type,int begin,int cnt)
{
}
/* -------------------以下部分是用户程序-----------------*/
enum VAO_IDs { Triangles,Triangles1, NumVAOs }; 
enum Buffer_IDs { ArrayBuffer,ArrayBuffer1, NumBuffers }; 
enum Attrib_IDs { vPosition = 1 };
 
GLuint  VAOs[NumVAOs]; 
GLuint  Buffers[NumBuffers]; 
const GLuint NumVertices = 6;
void init(void)
{ 
        GLfloat  vertices[NumVertices][2] =
    { 
                { -0.90f, -0.90f },  // Triangle 1 
                {  0.85f, -0.90f }, 
                { -0.90f,  0.85f }, 
                {  0.90f, -0.85f },  // Triangle 2 
                {  0.90f,  0.90f }, 
                { -0.85f,  0.90f } 
        }; 
    /* 创建个vao和个vbo */
    glGenVertexArrays(NumVAOs, VAOs);
        glGenBuffers(NumBuffers, Buffers);
   
        /* 先绑定第一个vao,此时对vbo的操作全部与vao关联*/
        glBindVertexArray(VAOs[Triangles]); 
        glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]); 
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices)/2,  vertices, GL_STATIC_DRAW); 
        glVertexAttribPointer(vPosition, 2, GL_FLOAT,  GL_FALSE, 0, 0); 
        glEnableVertexAttribArray(vPosition); 
 
        /* 再绑定第二个vao,此时对vbo的操作全部与该vao关联*/
        glBindVertexArray(VAOs[Triangles1]);  
        glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer1]); 
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices)/2,  &vertices[3], GL_STATIC_DRAW); 
        glVertexAttribPointer(vPosition, 2, GL_FLOAT,  GL_FALSE, 0, 0); 
        glEnableVertexAttribArray(vPosition); 
} 
 
void display(void)
{ 
    static int i = 0; 
        /* 通过绑定来启动当前vao,就可以直接绘制相应的图形*/
    if(i++%2 == 0)
    {
        glBindVertexArray(VAOs[Triangles]); 
        glDrawArrays(GL_TRIANGLES, 0, NumVertices/2); 
    }
    else
    {
        glBindVertexArray(VAOs[Triangles1]); 
        glDrawArrays(GL_TRIANGLES, 0, NumVertices/2); 
    }
   
    printf("display i:%d\n",i);
} 
int main(int argc, char** argv)
{ 
        init();
        display();
        return 0;
}

    以上程序分为2部分,上面的不是是对缓冲对象操作逻辑的模拟,后面是应用程序,程序结果输出不重要。程序的重点:

1. 缓冲对象可以认为是用结构体数组管理的,缓冲对象操作的对象是当前(cur)对象。

2. 缓冲对象glGen(创建)的过程可以认为是分配数组下标,实际上分配出的缓冲对象的值都是从0或者1开始的(0有时候是保留对象),很像是数组的下标。

3. bind过程,可以理解为把当前的缓冲对象指针指向指定的缓冲对象。 

3. 使用ebo简化绘制过程

    直接通过一个例子来说明ebo的用途和用法,下面的例子是绘制2个田字形的格子,分别使用线和四边形的方式绘制,考虑一下,如果不使用ebo,设计坐标的时候就需要考虑绘制方式,用线的方式绘制时,坐标传送应该用线的方式,如例子中的田字格,使用GL_LINES方式绘制,需设计48float型的坐标(共12条短线,每条线2个坐标,每个坐标2float 数据),而使用GL_QUADS方式绘制,需要设计32float型坐标,很麻烦,可以看出来,中间的很多坐标都是相同的。

    这个时候使用ebo就非常简单了,先设计田字格的9个顶点,然后再设计绘制方式的索引,顶点设计和绘制方法设计完全分开,使程序更明了。

    ebovbo使用方式类似,先使用glGenBuffers创建ebo,再使用glBindBuffer绑定ebo,然后使用glBindBuffer传送数据,注意类型为GL_ELEMENT_ARRAY_BUFFER,绘图的时候使用glDrawElements函数。

    例子源码如下:

#include <stdlib.h>
#include <stdio.h>
#include <GL/glew.h>
#include <GL/glut.h>
static const GLchar * vertex_source =
    "#version 330 core\n"
    "uniform float translate;\n"
    "layout (location = 0) in vec2 position;\n"
    "layout (location = 1) in vec3 color;\n"
    "flat out vec3 vertex_color;\n"
    "void main()\n"
    "{\n"
    "gl_Position = vec4(position.x+translate,position.y,0.0,1.0);\n"
    "vertex_color = color;\n"
    "}\0";
static const GLchar * frag_source =
    "#version 330 core\n"
    "flat in vec3 vertex_color;\n"
    "out vec4 color;\n"
    "void main()\n"
    "{\n"
    "color = vec4(vertex_color,1.0);\n"
    "}\n\0";
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
    const GLchar * shaderSource[] = {source};
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, shaderSource, 0);
    glCompileShader(shader);
    glAttachShader(program, shader);
}
GLint translate_loc;
GLuint vao, vbo, ebo[2];
void init()
{
    GLuint program = glCreateProgram();
    /* 同时加载了顶点着色器和片元着色器*/
    loadShader(program, GL_VERTEX_SHADER, vertex_source);
    loadShader(program, GL_FRAGMENT_SHADER, frag_source);
    glLinkProgram(program);
    glUseProgram(program);
    translate_loc = glGetUniformLocation(program, "translate");
    /* 田字格的顶点坐标*/
    GLfloat vertices[][2] =
    {
        { -0.9f, 0.5f}, { -0.5f, 0.5f}, { -0.1f, 0.5f},         /* 第一行*/
        { -0.9f, 0.0f}, { -0.5f, 0.0f}, { -0.1f, 0.0f},         /* 第二行*/
        { -0.9f, -0.5f}, { -0.5f, -0.5f}, { -0.1f, -0.5f},      /* 第三行*/
    };
    /* 每个点给一种颜色*/
    GLfloat colors[][3] =
    {
        {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f},
        {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f},
        {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f},
    };
    /* 线的方式画田字格,每条线个点*/
    GLint index_line[][2] =
    {
        {0, 1}, {1, 2}, /* 第一行线索引*/
        {3, 4}, {4, 5},
        {6, 7}, {7, 8},
        {0, 3}, {3, 6}, /* 第一列线索引*/
        {1, 4}, {4, 7},
        {2, 5}, {5, 8},
    };
    /* 四边形的方式画田字格,每条四边形个点*/
    GLint index_quad[][4] =
    {
        {0, 1, 4, 3},           /* 第一个格子*/
        {1, 2, 5, 4},
        {3, 4, 7, 6},
        {4, 5, 8, 7},
    };
    glGenBuffers(1, &vao);
    glBindBuffer(GL_ARRAY_BUFFER, vao);
        /* 顶点数组和颜色数据放同一个vbo中,设置时候注意数据宽度和偏移*/
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colors), NULL, GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colors), colors);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(vertices)));
    glEnableVertexAttribArray(1);
        /* 创建画格子方式的索引,也可以用一个ebo,通过偏移量来存放索引*/
    glGenBuffers(2, ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);      /* 存放用线画田字格的索引*/
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_line), index_line, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);      /* 存放用线画田字格的索引*/
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_quad), index_quad, GL_STATIC_DRAW);
        glLineWidth(2.0);
    glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
        glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
}
void display()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glBindBuffer(GL_ARRAY_BUFFER, vao);
        /* 12根线,每根个索引*/
        glUniform1f(translate_loc,0.0f);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
        glDrawElements(GL_LINES,2*12, GL_UNSIGNED_INT, (GLvoid *)(0));
        /* 用偏移的方式画第二个田字格*/
        glUniform1f(translate_loc,1.0f);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[1]);
        glDrawElements(GL_QUADS,4*4, GL_UNSIGNED_INT, (GLvoid *)(0));
    glFlush();
}
int main(int argc, char * argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(600, 400);
    glutCreateWindow("ebo");
    glewInit();
    init();
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

    效果如下:

 注意:

1. 为方便看效果,例子画图的颜色没有使用默认的渐变色,使用了glProvokingVertex禁用渐变(参考第3章介绍)。

2. 传送顶点坐标和颜色时,使用了glBufferSubData,该函数作用是分段传数据,与glBufferData 配合使用,注意偏移值。

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值