在GL中特别提出了缓冲区对象这一概念,是针对提高绘图效率的一个手段。由于GL的架构是基于客户——服务器模型建立的,因此默认所有的绘图数据均是存储在本地客户端,通过GL内核渲染处理以后再将数据发往GPU显示。假设这样一种情况,有一批数据并不经常更改而数目又挺大,如果按照通常的做法无非是不断重复不断重复发送数据,整个操作主要内容变成了传输数据。针对这种窘况,GL内核允许客户将客户端的数据直接在GPU显存区域开辟一段缓冲区存储,存储具备这种特点的数据。
此时,考虑另一种极端情况,绘图数据需要实时渲染更新,此时若也使用缓冲区对象则其资源、操作开销的优势就没有那么明显,这种情况不如按照通常的手段。
另外,说起缓存区很容易让人联想到内存,下意识认为new或者malloc也能动态开辟内存,这其实是一个误区。首先,应用程序都是加载在内存里运行无需你再开辟缓存,本身就是缓存;其次,硬件制造商针对GL内核开放显存访问接口使得我们只需用一些函数就能直接访问显存。
下面就以一个按键缩放立方体的例子说明如何使用GPU的显存,相关函数的使用在代码旁边都做了简单解释。
注意点:
(1)新添glext扩展库,用于支持访问显存。具体添加方法参照第一篇《OpenGL学习笔记0——安装库》
(2)GL根据缓冲区对象的类型将相同的类型的缓冲区对象“连续存放”——同是定点类的数据存放在一起,同是索引类的数据存放在一起,展现给用户的是连续存放的显存。猜想,显存驱动或者GL内核通过类似“句柄”这种操作隐藏了同类数据在缓存中分开存储的这一事实。因此在调用缓冲区数据进行绘图操作时候要特别特别小心偏移量的问题,默认NULL是某类缓冲区对象起始地址。这样的做法带来的缺点是需要程序员来定义好偏移地址不能互相覆盖,然而带来的好处就是一类缓冲区只对应唯一一个“指针”(该类起始地址为NULL的内存地址)。因此,在调用的时候你就发现相关的接口是不返回或者接受任何指针的,唯一需要你填写的就是偏移地址。这一点和普通的主板内存申请和释放操作是不太一样的。
1 #pragma comment(lib,"glut32.lib") 2 #pragma comment(lib,"glut.lib") 3 #pragma comment(lib,"GlU32.lib") 4 #pragma comment(lib,"glext.lib") 5 #include<GL\glut.h> 6 #include<GL\glext.h> 7 #include<Windows.h> 8 //global variables 9 GLfloat vertices[] = { 10 -0.5f, -0.5f, -0.5f, 11 0.5f, -0.5f, -0.5f, 12 -0.5f, 0.5f, -0.5f, 13 0.5f, 0.5f, -0.5f, 14 -0.5f, -0.5f, 0.5f, 15 0.5f, -0.5f, 0.5f, 16 -0.5f, 0.5f, 0.5f, 17 0.5f, 0.5f, 0.5f, 18 }; 19 GLubyte indices[][4] = { 20 0, 2, 3, 1, 21 0, 4, 6, 2, 22 0, 1, 5, 4, 23 4, 5, 7, 6, 24 1, 3, 7, 5, 25 2, 6, 7, 3, 26 }; 27 GLuint VertexBuffer[1]; 28 GLuint VertexIndexBuffer[1]; 29 GLfloat rotate; 30 GLfloat* ptr; 31 //func protype 32 void CubeInit(void); 33 void GetKey(unsigned char key, int x, int y); 34 void Display(void); 35 void Timer0(int id); 36 //main func 37 int main(int argc, char* argv[]) 38 { 39 glutInit(&argc, argv); 40 glutInitDisplayMode(GL_DOUBLE | GLUT_RGBA); 41 glutInitWindowSize(500, 500); 42 glutCreateWindow("cube-vertex-buffer"); 43 glutKeyboardFunc(GetKey); 44 glutDisplayFunc(Display); 45 glutTimerFunc(20,Timer0,0); 46 CubeInit(); 47 glutMainLoop(); 48 } 49 //创建立方体的顶点数组、开辟一块对应的顶点缓存区 50 void CubeInit(void) 51 { 52 glGenBuffers(1,VertexBuffer);//创建用于存储顶点数据的一块缓存区 53 glBindBuffer(GL_ARRAY_BUFFER,VertexBuffer[0]); //与之绑定 54 glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_DYNAMIC_DRAW);//将顶点数据拷贝到该缓存区域 55 56 glGenBuffers(1,VertexIndexBuffer);//创建用于存储顶点数组索引的一块缓存区 57 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,VertexIndexBuffer[0]);//与之绑定 58 glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);//将顶点数组索引拷贝到该缓 59 60 glEnableClientState(GL_VERTEX_ARRAY);//开启顶点数组 61 glVertexPointer(3,GL_FLOAT,3*sizeof(GLfloat),NULL);//将缓存区的顶点数据缓存内容存储到定点数组里,偏移量为0,即null 62 glDrawElements(GL_QUADS,24,GL_UNSIGNED_BYTE,NULL);//将缓存区的顶点索引存储进去,偏移量0,即null 63 64 //默认的多边形表面是以填充的形式绘制,此处设置为轮廓线绘制 65 glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); 66 //设定逆时针为正面 67 glFrontFace(GL_CCW); 68 } 69 void Display(void) 70 { 71 glClearColor(0.0f,0.0f,0.0f,0.0f); 72 glClear(GL_COLOR_BUFFER_BIT); 73 glDrawElements(GL_QUADS,24,GL_UNSIGNED_BYTE,NULL); 74 glutSwapBuffers(); 75 } 76 void GetKey(unsigned char key, int x, int y) 77 { 78 switch (key) 79 { 80 case'a': 81 ptr = (GLfloat*)glMapBuffer(GL_ARRAY_BUFFER,GL_READ_WRITE); 82 for (int i =0; i <24; i ++) 83 { 84 *(ptr + i) *=0.9f; 85 } 86 glUnmapBuffer(GL_ARRAY_BUFFER); 87 glutPostRedisplay(); 88 break; 89 default:break; 90 } 91 } 92 void Timer0(int id) 93 { 94 glClear(GL_COLOR_BUFFER_BIT); 95 glMatrixMode(GL_MODELVIEW); 96 glLoadIdentity(); 97 glPushMatrix(); 98 glLineWidth(2.4); 99 //由于采用了矩阵压栈和出栈的处理,使得此处旋转的角度采用全局变量,存储角度 100 //旋转角度实则对360°为一个周期。 101 rotate += 0.2; 102 glRotatef(rotate,1,0,0); 103 glRotatef(rotate,0,1,0); 104 glRotatef(rotate,0,0,1); 105 glDrawElements(GL_QUADS,24,GL_UNSIGNED_BYTE,NULL); 106 glPopMatrix(); 107 glutSwapBuffers(); 108 glutTimerFunc(20,Timer0,0);//for continue timer counting 109 }