图形渲染管线指的是对一些原始数据通过一系列的处理变换并最终把这些数据输出到屏幕上的整个过程。数组
图形渲染管线的整个处理流程能够被划分为几个阶段,上一个阶段的输出数据做为下一个阶段的输入数据,是一个串行的,面向过程的执行过程。每个阶段分别在GPU上运行各自的数据处理程序,这个程序就是着色器。缓存
部分着色器容许咱们使用着色语言(OpenGL Shading Language)编写自定义的着色器,这样就能够更为细致的控制图像渲染流程中的特定处理过程了,下图是一个图形渲染管线每个阶段的抽象表示,蓝色部分表明容许自定义着色器。ide
顶点数据是一些顶点的集合,顶点通常是3维的点坐标组成。函数
基本图元(Primitives)包括点,线段,三角形等,是构成实体模型的基本单位,须要在传入顶点数据的同时通知OpenGL这些顶点数据要组成的基本图元类型。oop
顶点着色器(Vertex Shader)包含对一些顶点属性(数据)的基本处理。测试
基本图元装配(Primitive Assembly)把全部输入的顶点数据做为输入,输出制定的基本图元。ui
几何着色器(Geometry Shader)把基本图元形式的顶点的集合做为输入,能够经过产生新顶点构造出新的(或是其余的)基本图元来生成其余形状。spa
细分着色器(Tessellation Shaders)能够把基本图元细分为更多的基本图形,建立出更加平滑的视觉效果。3d
光栅化(Rasterization)即像素化,把细分着色器输出的基本图形映射为屏幕上网格的像素点,生成供片断着色器处理的片断(Fragment),光栅化包含一个剪裁操做,会舍弃超出定义的视窗以外的像素。code
片断着色器(Fragment Shader)的主要做用是计算出每个像素点最终的颜色,一般片断着色器会包含3D场景的一些额外的数据,如光线,阴影等。
测试与混合是对每一个像素点进行深度测试,Alpha测试等测试并进行颜色混合的操做,这些测试与混合操做决定了屏幕视窗上每一个像素点最终的颜色以及透明度。
在整个渲染管线中须要自定义处理的主要是顶点着色器和片断着色器。
顶点缓冲对象VBO是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各种属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,能够直接从VBO中取出顶点的各种属性数据,因为VBO在显存而不是在内存中,不须要从CPU传输数据,处理效率更高。
因此能够理解为VBO就是显存中的一个存储区域,能够保持大量的顶点属性信息。而且能够开辟不少个VBO,每一个VBO在OpenGL中有它的惟一标识ID,这个ID对应着具体的VBO的显存地址,经过这个ID能够对特定的VBO内的数据进行存取操做。
VBO的建立以及配置
建立VBO的第一步须要开辟(声明/得到)显存空间并分配VBO的ID:
//建立vertex buffer object对象 GLuint vboId;//vertex buffer object句柄 glGenBuffers(1, &vboId);
建立的VBO可用来保存不一样类型的顶点数据,建立以后须要经过分配的ID绑定(bind)一下制定的VBO,对于同一类型的顶点数据一次只能绑定一个VBO。绑定操做经过glBindBuffer来实现,第一个参数指定绑定的数据类型,能够是GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER或者GL_PIXEL_UNPACK_BUFFER中的一个。
glBindBuffer(GL_ARRAY_BUFFER, vboId);
接下来调用glBufferData把用户定义的数据传输到当前绑定的显存缓冲区中。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
顶点数据传入GPU以后,还须要通知OpenGL如何解释这些顶点数据,这个工做由函数glVertexAttribPointer完成:
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
顶点属性glVertexAttribPointer默认是关闭的,使用时要以顶点属性位置值为参数调用glEnableVertexAttribArray开启。如glEnableVertexAttribArray(0);
VBO保存了一个模型的顶点属性信息,每次绘制模型以前须要绑定顶点的全部信息,当数据量很大时,重复这样的动做变得很是麻烦。VAO能够把这些全部的配置都存储在一个对象中,每次绘制模型时,只须要绑定这个VAO对象就能够了。
VAO是一个保存了全部顶点数据属性的状态结合,它存储了顶点数据的格式以及顶点数据所需的VBO对象的引用。
VAO自己并无存储顶点的相关属性数据,这些信息是存储在VBO中的,VAO至关因而对不少个VBO的引用,把一些VBO组合在一块儿做为一个对象统一管理。
VAO的建立和配置
生成一个VAO对象并绑定:
//建立vertex array object对象 GLuint vaoId;//vertext array object句柄 glGenVertexArrays(1, &vaoId); glBindVertexArray(vaoId);
执行VAO绑定以后其后的全部VBO配置都是这个VAO对象的一部分,能够说VBO是对顶点属性信息的绑定,VAO是对不少个VBO的绑定。
OpenGL中全部的图形都是经过分解成三角形的方式进行绘制,glDrawArrays函数负责把模型绘制出来,它使用当前激活的着色器,当前VAO对象中的VBO顶点数据和属性配置来绘制出来基本图形。
glDrawArrays (GLenum mode, GLint first, GLsizei count)
第二个参数定义从缓存中的哪一位开始绘制,通常定义为0;
第三个参数定义绘制的顶点数量;
索引缓冲对象EBO至关于OpenGL中的顶点数组的概念,是为了解决同一个顶点多洗重复调用的问题,能够减小内存空间浪费,提升执行效率。当须要使用重复的顶点时,经过顶点的位置索引来调用顶点,而不是对重复的顶点信息重复记录,重复调用。
EBO中存储的内容就是顶点位置的索引indices,EBO跟VBO相似,也是在显存中的一块内存缓冲器,只不过EBO保存的是顶点的索引。
建立EBO并绑定,用glBufferData(以GL_ELEMENT_ARRAY_BUFFER为参数)把索引存储到EBO中:
GLuint EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
当用EBO绑定顶点索引的方式绘制模型时,须要使用glDrawElements而不是glDrawArrays:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
Talk is cheap,第一个例子是使用VBO,VAO绘制一个矩形图形:
//使用VAO VBO绘制矩形 #include <GL/glew.h> #include <GL/freeglut.h> void userInit(); //自定义初始化 void reshape(int w, int h); //重绘 void display(void); void keyboardAction(unsigned char key, int x, int y); //键盘退出事件 GLuint vboId;//vertex buffer object句柄 GLuint vaoId;//vertext array object句柄 GLuint programId;//shader program 句柄 int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowPosition(100, 100); glutInitWindowSize(512, 512); glutCreateWindow("Rectangle demo"); //使用glew,须要执行glewInit,否则运行过程会报错 //glewInit要放在glut完成了基本的初始化以后执行 glewInit(); //自定义初始化,生成VAO,VBO对象 userInit(); //重绘函数 glutReshapeFunc(reshape); glutDisplayFunc(display); //注册键盘按键退出事件 glutKeyboardFunc(keyboardAction); glutMainLoop(); return 0; } //自定义初始化函数 void userInit() { glClearColor(0.0, 0.0, 0.0, 0.0); //建立顶点数据 const GLfloat vertices[] = { -0.5f,-0.5f,0.0f,1.0f, 0.5f,-0.5f,0.0f,1.0f, 0.5f,0.5f,0.0f,1.0f, -0.5f,0.5f,0.0f,1.0f, }; //建立VAO对象 glGenVertexArrays(1, &vaoId); glBindVertexArray(vaoId); //建立VBO对象 glGenBuffers(1, &vboId); glBindBuffer(GL_ARRAY_BUFFER, vboId); //传入VBO数据 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //解除VBO绑定 glBindBuffer(GL_ARRAY_BUFFER, 0); } //调整窗口大小回调函数 void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); } //绘制回调函数 void display(void) { glClear(GL_COLOR_BUFFER_BIT); //绑定VBO glBindBuffer(GL_ARRAY_BUFFER, vboId); glEnableVertexAttribArray(0); //解释顶点数据方式 glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //绘制模型 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableVertexAttribArray(0); glutSwapBuffers(); } //键盘按键回调函数 void keyboardAction(unsigned char key, int x, int y) { switch (key) { case 033: // Escape key exit(EXIT_SUCCESS); break; } }
编译并执行:
第二个例子使用EBO绘制两个三角形,组成一样的矩形图形:
//使用EBO绘制矩形(两个三角形) #include <GL/glew.h> #include <GL/freeglut.h> void userInit(); //自定义初始化 void reshape(int w, int h); //重绘 void display(void); void keyboardAction(unsigned char key, int x, int y); //键盘退出事件 GLuint eboId;//element buffer object句柄 GLuint vboId;//vertext buffer object句柄 GLuint vaoId;//vertext array object句柄 int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); glutInitWindowPosition(100, 100); glutInitWindowSize(512, 512); glutCreateWindow("Rectangle demo"); //使用glew,须要执行glewInit,否则运行过程会报错 //glewInit要放在glut完成了基本的初始化以后执行 glewInit(); //自定义初始化,生成VAO,VBO,EBO userInit(); //重绘函数 glutReshapeFunc(reshape); glutDisplayFunc(display); //注册键盘按键退出事件 glutKeyboardFunc(keyboardAction); glutMainLoop(); return 0; } //自定义初始化函数 void userInit() { glClearColor(0.0, 0.0, 0.0, 0.0); //建立顶点数据 const GLfloat vertices[] = { -0.5f,-0.5f,0.0f,1.0f, 0.5f,-0.5f,0.0f,1.0f, 0.5f,0.5f,0.0f,1.0f, -0.5f,0.5f,0.0f,1.0f, }; // 索引数据 GLshort indices[] = { 0, 1, 3, // 第一个三角形 1, 2, 3 // 第二个三角形 }; //建立VAO对象 glGenVertexArrays(1, &vaoId); glBindVertexArray(vaoId); //建立VBO对象,把顶点数组复制到一个顶点缓冲中,供OpenGL使用 glGenBuffers(1, &vboId); glBindBuffer(GL_ARRAY_BUFFER, vboId); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //建立EBO对象 glGenBuffers(1, &eboId); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); //传入EBO数据 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //解释顶点数据方式 glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0); //解绑VAO glBindVertexArray(0); //解绑EBO glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //解绑VBO glBindBuffer(GL_ARRAY_BUFFER, 0); } //调整窗口大小回调函数 void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); } //绘制回调函数 void display(void) { glClear(GL_COLOR_BUFFER_BIT); //绑定VAO glBindVertexArray(vaoId); //绘制模型 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL); glutSwapBuffers(); } //键盘按键回调函数 void keyboardAction(unsigned char key, int x, int y) { switch (key) { case 033: // Escape key exit(EXIT_SUCCESS); break; } }