OpenGL学习:VAO和VBO以及几种顶点绘图方式比较

一.为什么要有opengl缓冲区对象

1.在给shader传递顶点数据的时候,如果每次都要向GPU发送一大块数据,而这数据其实并没有修改过,那么这传输就是冗余的。所以这里添加了缓冲区对象,将顶点数组存储在服务器端的缓冲区对象中。

2.在GPU中,VBO(vertex buffer object)负责实际数据的存储;而VAO(vertex array object)则记录数据的存储和如何使用的细节信息。

3.使用VAO的优势就在于,如果有多个物体需要绘制,那么我们设置一次绘制物体需要的顶点数据、数据解析方式等信息,然后通过VAO保存起来后,后续的绘制操作不再需要重复这一过程,只需要将VAO设定为当前VAO,那么OpenGL则会使用这些状态信息。当场景中物体较多时,优势十分明显。VAO和VBO的关系如下图所示:

               这里写图片描述

      上图中表示,顶点属性包括位置、纹理坐标、法向量、颜色等多个属性,每个属性的数据可以存放在不同的buffer中。我们可以根据需求,在程序中创建多个VBO和VAO

二.如何创建和使用缓冲区对象

  1.创建VBO
    glGenBuffers():OpenGL分配n个当前未使用的名称(非 0),表示缓冲区对象。
    原型 : void glGenBuffers(GLsizei n,GLuint *buffers);
GLuint VBOId;glGenBuffers(1,&VBOId);
 2.绑定缓冲区对象
    缓冲区对象,首先需要对他进行绑定。绑定缓冲区对象表示选择未来的操作将影响到哪个缓冲区对象。
    glBindBuffer(GLenum target ,GLuint buffer): 指定了当前的活动缓冲区对象 。
glBindBuffer(GL_ARRAY_BUFFER,VBOId) ;
  3.将顶点数据传送到VBO
    void glBufferData(GLenum target,GLsizeiptr size,const GLvoid * data,GLenum usage );
    功能是分配size个存储单位的OpenGL服务器内存,用于存储顶点数据或索引。以前有与当前缓冲区对象相关联的数据将删除。
a.函数中target参数表示绑定的目标,包括像GL_ARRAY_BUFFER用于Vertex attributes(顶点属性),GL_ELEMENT_ARRAY_BUFFER用于索引绘制等目标。 
b.size参数表示需要分配的空间大小,以字节为单位。 
c.data参数用于指定数据源,如果data不为空将会拷贝其数据来初始化这个缓冲区,否则只是分配预定大小的空间。预分配空间后,后续可以通过glBufferSubData来更新缓冲区内容。 
d.usage参数指定数据使用模式,例如GL_STATIC_DRAW指定为静态绘制,数据保持不变, GL_DYNAMIC_DRAW指定为动态绘制,数据会经常更新
   4.通知OpenGL如何解释这个顶点属性数组
将数据传送到GPU后,我们还需要告知OpenGL如何解释这个数据
void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer); 
a. 参数index 表示顶点属性的索引 这个索引即是在顶点着色器中的属性索引,索引从0开始记起。  
b. 参数size 每个属性数据由几个分量组成。例如上面顶点每个属性为3个float组成的,size即为3。分量的个数必须为1,2,3,4
c. 参数type表示属性分量的数据类型,例如上面的顶点数据为float则填写GL_FLOAT.  
d. 参数normalized 表示是否归一化,当存储整型时,如果设置为GL_TRUE,那么当被以浮点数形式访问时,有符号整型转换到[-1,1],无符号转换到[0,1]。否则直接转换为float型,而不进行归一化  
e. 参数stride表示连续的两个顶点属性之间的间隔,以字节大小计算。当顶点属性紧密排列(tightly packed)时,可以填0,由OpenGL代替我们计算出该值。  
f. 参数pointer表示当前绑定到 GL_ARRAY_BUFFER缓冲对象的缓冲区中,顶点属性的第一个分量距离数据的起点的偏移量,以字节为单位计算。

这里以绘制一个三角形为例,这个三角形包含的顶点属性包括:位置、颜色、以及纹理坐标,着色器中将颜色和纹理进行混合。有了上面对glVertexAttribPointer的理解,我们可以有三种方式传送顶点数据:

  • 为每个顶点属性指定一个独立的VBO

  • 单个顶点属性连续存放,整个顶点属性作为一个一个VBO

  • 顶点属性之间交错存放,整体作为一个VBO

这三种方式都是支持的,通过这三种方式的实现,我们加深对glVertexAttribPointer的理解,具体的实现方法见如下:
http://blog.csdn.net/wangdingqiaoit/article/details/52662270

   5.更新缓冲区对象的数据值
    方法一:
glBufferSubData
用来更新缓冲区对象中的数据,可以更新一部分或者整个缓冲区中的数据(取决于offset和size的取值),如果offset是0,而size正好是用来缓冲区存储数据的大小,那么整个数据就会被新的数据所替换。            
    方法二:允许灵活的选择需要更新的数据。
            GLvoid *glMapBuffer(GLenum target,GLuenum access):返回一个指向缓冲区对象的指针,可以在这个缓冲区对象中写入新值及更新之后,再调用GLboolean glUnMapBuffer(GLenum target)表示已经完成了对数据的更新,取消对这个缓冲区的映射。如果只需要更新所需范围内的数据值,也可调用GLvoid *glMapBuffwerRange
更多关于glBufferSubData和glMapBuffer传送顶点数据的方法参考下面链接:
http://blog.csdn.net/wangdingqiaoit/article/details/52662270
   6.在缓冲区对象之间复制数据
     void glCopyBufferSubData(readbuffer,writebuffer,resdoffset,writeoffset,size);
     把数据从与readbuffer相关联的缓冲区对象复制到绑定到writebuffer的缓冲区对象。

二. example:几种绘图方式比较

1.采用顶点数组绘制三角形

使用顶点数组方式,需要利用glEnableClientState(GL_VERTEX_ARRAY)开启顶点特性。用户定义好存储顶点的数据,在调用glDrawArrays、glDrawElements之类的函数时,通过glVertexPointer设定的指针,传送数据到GPU。当调用完glDrawArrays后,GPU中已经有了绘图所需数据,这时可以释放数据空间

#  include   
#  include   
struct vec3f {   
    GLfloat x, y, z;  };  
int main( int argc, char **argv )  
{  
    glutInit(&argc, argv);    
    glutInitDisplayMode( GLUT_RGBA|GLUT_SINGLE);  
    glutInitWindowPosition(100,100);  
    glutInitWindowSize( 512, 512 );  
    glutCreateWindow( "Triangle demo" );       
    glewInit();  
    userInit();  
    glutReshapeFunc(reshape);  
    glutDisplayFunc( display );  
    glutKeyboardFunc( keyboardAction );  
    glutMainLoop();  
    return 0;  
}  
void userInit()  
{  
     glClearColor( 0.0, 0.0, 0.0, 0.0 );  
     glColor4f(1.0,1.0,0.0,0.0);  
}  
void reshape(int w,int h)  
{  
    glViewport(0,0,(GLsizei)w,(GLsizei)h);  
}  
void display( void )  
{  
    glClear( GL_COLOR_BUFFER_BIT);  
    //利用顶点数组,绘制三角形  
    const int num_indices = 3;  
     //创建保存顶点的结构体数组  
     vec3f *vertices = new vec3f[num_indices];  
     // 顶点1  
     vertices[0].x = -0.5f;   
     vertices[0].y = -0.5f;   
     vertices[0].z = 0.0f;   
     // 顶点2  
     vertices[1].x = 0.5f;   
     vertices[1].y = 0.0f;   
     vertices[1].z = 0.0f;   
     //顶点3  
     vertices[2].x = 0.0f;   
     vertices[2].y = 0.5f;  
     vertices[2].z = 0.0f;  
     // 启用vertex arrays   
     glEnableClientState(GL_VERTEX_ARRAY);  
     //定义顶点数组  
     glVertexPointer(   
         3,         // 每个顶点的维度   
         GL_FLOAT,  // 顶点数据类型  
         0,         // 连续顶点之间的间隙,这里为0  
         vertices   //指向第一个顶点的第一个坐标的指针  
    );  
    glDrawArrays(GL_TRIANGLES, 0, num_indices);  
    glDisableClientState(GL_VERTEX_ARRAY);  
    //释放内存空间  
    delete[] vertices;  
    glFlush();  
}   
void keyboardAction( unsigned char key, int x, int y )  
{  
    switch( key )   
    {  
        case 033:  // Escape key  
            exit( EXIT_SUCCESS );  
            break;  
    }  
} 
2.单独使用VBO绘制三角形
(这里并没有使用VAO,因此仍然要使用glEnableClientState(GL_VERTEX_ARRAY),下个例子演示了VAO与VBO结合例子)
//g++ vbo.cpp -lGL -lGLEW -lglut
#  include 
#  include 
GLuint vboId;
int main( int argc, char **argv )
{
    glutInit(&argc, argv);
    glutInitDisplayMode( GLUT_RGBA|GLUT_SINGLE);
    glutInitWindowPosition(100,100);
    glutInitWindowSize( 512, 512 );
    glutCreateWindow( "Triangle demo" );
    glewInit();
    userInit();
    glutReshapeFunc(reshape);
    glutDisplayFunc( display );
    glutKeyboardFunc( keyboardAction );
    glutMainLoop();
    return 0;
}
void userInit()
{
     glClearColor( 0.0, 0.0, 0.0, 0.0 );
     glColor4f(1.0,1.0,0.0,0.0);
     GLfloat vertices[] = {
        -0.5,-0.5,0.0,
        0.5,0.0,0.0,
        0.0,0.5,0.0
     };
     glGenBuffersARB(1,&vboId);
     glBindBufferARB(GL_ARRAY_BUFFER_ARB,vboId);
     glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(vertices),vertices,GL_STATIC_DRAW_ARB);
     glBindBufferARB(GL_VERTEX_ARRAY,0);
}
void reshape(int w,int h)
{
    glViewport(0,0,(GLsizei)w,(GLsizei)h);
}
void display( void )
{
    glClear( GL_COLOR_BUFFER_BIT);
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glDisableClientState(GL_VERTEX_ARRAY);
    glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
    glFlush();
}
void keyboardAction( unsigned char key, int x, int y )
{
    switch( key )
    {
        case 033:  // Escape key
            exit( EXIT_SUCCESS );
            break;
    }
}
3.用VAO+VBO结合shader的方式绘制三角形
// g++ triangle.cpp -lGLEW -lglfw3
#define GLEW_STATIC
#include 
#include 
#include 
#include 
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
const int WINDOW_WIDTH = 800, WINDOW_HEIGHT = 600;
int main(int argc, char** argv)
{
	if (!glfwInit())	
	{
		std::cout << "Error::GLFW could not initialize GLFW!" << std::endl;return -1;
	}
	std::cout << "Start OpenGL core profile version 3.3" << std::endl;
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

	GLFWwindow* window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT,"Demo of triangle", NULL, NULL);	
	if (!window)
	{
		std::cout << "Error::GLFW could not create winddow!" << std::endl;glfwTerminate();return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback);

	// 初始化GLEW获取OpenGL函数
	glewExperimental = GL_TRUE; //让GLEW获取所有extension函数
	GLenum status = glewInit();
	if (status != GLEW_OK)
	{
		std::cout << "Error::GLEW glew version:" << glewGetString(GLEW_VERSION)
			<< " error string:" << glewGetErrorString(status) << std::endl;
		glfwTerminate();return -1;
	}
	glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);

	// Section1 准备顶点数据
	GLfloat vertices[] = {
		-0.5f, 0.0f, 0.0f,
		0.5f, 0.0f, 0.0f,
		0.0f, 0.5f, 0.0f };
	GLuint VAOId, VBOId;
	// Step1: 创建并绑定VAO
	glGenVertexArrays(1, &VAOId);
	glBindVertexArray(VAOId);
	// Step2:创建并绑定VBO
	glGenBuffers(1, &VBOId);
	glBindBuffer(GL_ARRAY_BUFFER, VBOId);
	// Step3: 分配空间传送数据
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// Step4: 指定GPU解析数据的方式
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), (GLvoid*)0);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	// Section2 准备shader
	// Step1: shader source
	const GLchar* vertexShaderSource = "#version 330\n"
		"layout(location = 0) in vec3 position;\n"
		"void main()\n"
		"{\n gl_Position = vec4(position, 1.0);\n}";
	const GLchar* fragShaderSource = "#version 330\n"
		"out vec4 color;\n"
		"void main()\n"
		"{\n color = vec4(0.8, 0.8, 0.0, 1.0);\n}";
	// Step2 create Shader object
	// vertex shader
	GLuint vertexShaderId = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShaderId, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShaderId);
	GLint compileStatus = 0;
	glGetShaderiv(vertexShaderId, GL_COMPILE_STATUS, &compileStatus);
	if (compileStatus == GL_FALSE) 
	{
		GLint maxLength = 0;
		glGetShaderiv(vertexShaderId, GL_INFO_LOG_LENGTH, &maxLength);
		std::vector errLog(maxLength);
		glGetShaderInfoLog(vertexShaderId, maxLength, &maxLength, &errLog[0]);
		std::cout << "Error::shader vertex shader compile failed," << &errLog[0] << std::endl;
	}
	// fragment shader
	GLuint fragShaderId = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragShaderId, 1, &fragShaderSource, NULL);
	glCompileShader(fragShaderId);
	glGetShaderiv(fragShaderId, GL_COMPILE_STATUS, &compileStatus);
	if (compileStatus == GL_FALSE)
	{
		GLint maxLength = 0;
		glGetShaderiv(fragShaderId, GL_INFO_LOG_LENGTH, &maxLength);
		std::vector errLog(maxLength);
		glGetShaderInfoLog(fragShaderId, maxLength, &maxLength, &errLog[0]);
		std::cout << "Error::shader fragment shader compile failed," << &errLog[0] << std::endl;
	}
	// Step3 link shader
	GLuint shaderProgramId = glCreateProgram();
	glAttachShader(shaderProgramId, vertexShaderId);
	glAttachShader(shaderProgramId, fragShaderId);
	glLinkProgram(shaderProgramId);
	GLint linkStatus;
	glGetProgramiv(shaderProgramId, GL_LINK_STATUS, &linkStatus);
	if (linkStatus == GL_FALSE)
	{
		GLint maxLength = 0;
		glGetProgramiv(shaderProgramId, GL_INFO_LOG_LENGTH, &maxLength);
		std::vector errLog(maxLength);
		glGetProgramInfoLog(shaderProgramId, maxLength, &maxLength, &errLog[0]);
		std::cout << "Error::shader link failed," << &errLog[0] << std::endl;
	}
	// 链接完成之后可以detach shader
	glDetachShader(shaderProgramId, vertexShaderId);
	glDetachShader(shaderProgramId, fragShaderId);
	// 不需要连接到其他shader program时可以将这个shader释放
	glDeleteShader(vertexShaderId);
	glDeleteShader(fragShaderId);

	//绘图主循环
	while (!glfwWindowShouldClose(window))
	{
		glfwPollEvents(); // 捕捉鼠标键盘事件
		glClearColor(0.18f, 0.04f, 0.14f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		// 绘制
		glBindVertexArray(VAOId);
		glUseProgram(shaderProgramId);
		glDrawArrays(GL_TRIANGLES, 0, 3);

		glBindVertexArray(0);
		glUseProgram(0);
		glfwSwapBuffers(window); // swap
	}
	// release data
	glDeleteProgram(shaderProgramId);
	glDeleteVertexArrays(1, &VAOId);
	glDeleteBuffers(1, &VBOId);
	glfwTerminate();
	return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
	{
		glfwSetWindowShouldClose(window, GL_TRUE);
	}
}

你可能感兴趣的:(OpenGL学习)