OpengGL第八版的第一个例子

来源:互联网 发布:淘宝卖家怎么提高流量 编辑:程序博客网 时间:2024/06/05 04:59

不得不说,opengl第八版和第七版改动太大了。

第八版的第一个例子是按可编程渲染管线来讲的。网上很多pengl入门教程用的还是固定管线的知识。关于固定管线和可编程渲染管线的区别可以参考知乎:

可编程渲染和固定渲


关于图形绘制方式的比较以及为什么使用VAO VBO可以参考这篇文章:

基本图形绘制方式比较

为什么要使用VBO:

  •      使用立即模式的缺点很明显,数据量大一点的话,代码量增加,而且数据发送到服务端需要开销;
  • 使用显示列表,显示列表是一个服务端函数,因此它免除了传送数据的额外开销。但是,显示列表一旦编译后,其中的数据无法修改。
  •      使用顶点数组,可以减少函数调用和共享顶点数据的冗余。但是,使用顶点数组时,顶点数组相关函数是在客户端,因此数组中数据在每次被解引用时必须重新发送到服务端,额外开销不可忽视。
  •      使用VBO在服务端创建缓存对象,并且提供了访问函数来解引用数组;例如在顶点数组中使用的函数如 glVertexPointer(), glNormalPointer(), glTexCoordPointer()。同时,VBO内存管理会根据用户提示,"target"  和"usage"模式,将缓存对象放在最佳地方。因此内存管理会通过在系统内存、AGP内存和视频卡内存(system, AGP and video memory)这3中内存见平衡来优化缓存。另外,不像显示列表,VBO中数据可以通过映射到客户端内存空间而被用户读取和更新。VBO的另外一个优势是它像显示列表和纹理一样,能和多个客户端共享缓存对象。可见使用VBO优势很明显。
  • 为什么要配合VAO使用VBO:
  •    VBO存储了实际的数据,真正重要的不是它存储了数据,而是他将数据存储在GPU中。这意味着VBO它会很快,因为存在RAM中的数据需要被传送到GPU中,因此这个传送是有代价的。

    VAO代表的是一些描述存储在VBO中对象的属性。VAO可以被视为指向对象的高级内存指针,有点类似于C语言指针,但比地址多了跟多的跟踪作用。他们很复杂。

    VAO就像一个容器,可以把VBO中的各项属性组合在一起。

    VAO并不与VBO直接相关,进过初看起来如此。VAOs节省了设置程序所需的状态的时间。如果没有VAO,你需要调用一堆类似gl*之类的命令。

  • 下面有个例子说明为什么VAO和VBO要配合使用:

    // draw with VAO  glBindVertexArray(vaoId); // bind vao  glDrawElements(...);  glBindVertexArray(0);     // unbind vao    //不使用VAO的话,要不停的开关client states// draw without VAO  // need to set many states before drawing  glEnableClientState(GL_VERTEX_ARRAY); // enable client states  glEnableClientState(GL_NORMAL_ARRAY);  glBindBuffer(GL_ARRAY_BUFFER, vboId); // bind vbo  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboId);  glVertexPointer(3, GL_FLOAT, 0, 0); // vertex attributes  glNormalPointer(GL_FLOAT, 0, offset); // normal attributes  glDrawElements(...);  glBindBuffer(GL_ARRAY_BUFFER, 0); // unbind vbo  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);  glDisableClientState(GL_VERTEX_ARRAY);  glDisableClientState(GL_NORMAL_ARRAY);

服务端和客户端:在opengl编程中,服务端通常指各种应用程序,比如我有一幅图,这幅图想通过显卡绘制到屏幕上,那么这幅图的数据就是客户端数据;它需要传递到显存当中去显示,那么我的服务端就是opengl操控的部分。(个人理解,欢迎指正)

VBO顶点数据传输过程:

顶点数据传输过程

VBO实际上存储的是顶点的属性数据。比如顶点坐标,顶点颜色等通过顶点着色器可以设置的各种属性数据。它是一个内存数组,

比如我有一个数组,这个数组可能是客户端产生的坐标,也有可能是像素等

const GLfloat vertices[] = {          -0.5f,-0.5f,0.0f,1.0f,          0.5f,0.0f,0.0f,1.0f,          0.0f,0.5f,0.0f,1.0f       }; 
我需要把这些数据传输到opengl缓存当中。首先创建VBO,即vboId;然后把这个对象绑定到GL_ARRAY_BUFFER上,我们通过GL_ARRAY_BUFFER这个中介来把vertices数组中的数据传给vboId(同样,我们在后面组织数据的时候也是要先将vbo绑定到GL_ARRAY_BUFFER中)。至于为什么用GL_ARRAY_BUFFER,我暂时不清楚。传给vboId后,这些数据全都放在了一块内存区域。

这样完成了发送顶点数据到GPU的任务。但是BO中的数据时未格式化的,但这是OpenGL关心的。我们只是分配了BO,并填充了些随机二进制数据。现在我们需要告诉OpenGL,BO中有顶点数据,并告诉他顶点数据的格式。我们通过下面这样的代码来完成这一任务:

glBindBuffer(GL_ARRAY_BUFFER, vboId);//将GL_ARRAY_BUFFER与vboId绑定一起

glEnableVertexAttribArray(0);//打开0号属性(这个0号属性通过shader绑定在一起)

glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_SET(0));//将0号属性与vboId结合到一起。

第一个函数声明使用BO。第二个函数启动顶点属性数组,这个稍后解释。

第三个函数是关键的。glVertexAttribPointer,尽管,包含”Pointer”一词,但是实际上它处理的并不是指针,而是BO。

在渲染时,OpenGL从BO中提取顶点数据。我们要做的就是通告OpenGL存储在BO中的顶点数组中数据格式。也就是要告诉OpenGL如何解释BO中的数组。

在我们的案例中,数据格式如下

  •      表示位置的单个数据值以32位浮点数据存储,使用C/C++ float类型。
  •      每个位置由4个这样的值组成。
  •      每4个值之间没有间隙,数据值在数组中紧密相连。
  •      数组中第一个值在BO的开始处

glVertexAttribPointer 函数告诉了OpenGL所有这些情况。第三个参数制定了值得基本类型,即GL_FLOAT,对应32位浮点数据。第二个参数,指定多少个这样的值组成一个位置,即一个点。在这里,即4个值组成一个点。第5个参数指定数据间间隙,第6个参数指定BO中数据偏移量,0代表从BO的开始处算起。

第四个参数以后再做解释。

下面通过一个实例来说明,由于opengL第一个例子含有枚举类型,不方便看,我就自己改了一个类似的。

#include<iostream>#include"vgl.h"#include"LoadShaders.h"using namespace std;GLfloat vertices[][2] = {{ -0.5f, -0.0f },{ -0.5f, 0.5f },{ 0.5f, 0.5f },};GLfloat color[][3] = {{ 1.0f, 0.5f, 0.0f },{ 0.0f, 1.0f, 0.0f },{ 1.0f, 1.0f, 0.0f },};GLuint vao;GLuint vbo[2];void init(){glGenVertexArrays(1, &vao);glBindVertexArray(vao);glGenBuffers(2, vbo);//生成两个VBO,第一个存储顶点坐标数据,第二个存储定点颜色数据//将顶点坐标数据复制到vbo0中glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//将vertices指针指向的所有数据复制到vbo[0]中;glBindBuffer(GL_ARRAY_BUFFER, 0);//解绑定//将顶点颜色数据复制到vbo1中glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);glBindBuffer(GL_ARRAY_BUFFER, 0);ShaderInfo shaders[] = {{GL_VERTEX_SHADER,"triangles.vert"},{GL_FRAGMENT_SHADER,"triangles.frag"},{GL_NONE,NULL}};GLuint program = LoadShaders(shaders);glUseProgram(program);//将vbo0中的数据每两个一组放一块(因为vertic中是两个数据一组,如果vertic用(x,y,z)代表坐标,则应该是3个数据绑一组)glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);//当前操作的是vbo0;glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));//0代表着色器中的0号属性(这个属性可以绑定到vbo0和vbo1中,由于我们没有写shader,所以暂且将0号属性绑到vbo0中),GL_FALSE说明不进行归一化,BUFFER_OFFSET(0)由于当前操作的就是vbo0,并且我们想从第一个字节开始,所以偏移量就是0;如果我们想跳过第一组数据来画一条直线,那么BUFFER_OFFSET(sizeof(*vertices));glBindBuffer(GL_ARRAY_BUFFER, 0);//解绑定glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);//当前操作的是vbo1glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));//1代表vbo1glBindBuffer(GL_ARRAY_BUFFER, 0);//解绑定glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);}void display(){glClear(GL_COLOR_BUFFER_BIT);glBindVertexArray(vao);glDrawArrays(GL_TRIANGLES, 0, 3);//管理glDrawArray会从顶点着色器中获取位置属性,然后画图;所以我们如果想根据vbo2来画图的话,就必须在shader中将location == 1glFlush();}int main(int argc, char **argv){glutInit(&argc, argv);glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);glutInitWindowSize(400, 400);glutInitContextVersion(3, 1);glutInitContextProfile(GLUT_CORE_PROFILE);glutCreateWindow(argv[0]);//如果要使用glew相关的函数,那么一定要先对glew初始化。if (glewInit()){cout << "glew 初始化失败" << endl;exit(EXIT_FAILURE);}init();glutDisplayFunc(display);glutMainLoop();return 0;}

triangle.vert代码如下:

#version 330 corevoid main(){    gl_Position = vPosition;}

location = 0 意思就是,0号属性;in就是说0号属性变量的值是外部传进来的。在glVertexAttribPointer中,第一个参数0代表的就是0号属性,所以vPosition的值就是vbo0传过来的。如果我们让location = 1,那么在

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0,BUFFER_OFFSET(0));//1代表vbo1

这句话中,就会把vbo1的值传给vposition。

之后在display函数中,

glBindVertexArray(vao);

glDrawArrays(GL_TRIANGLES, 0, 3);

glDrawArray会从顶点着色器中获取位置属性,然后画图;所以我们如果想根据vbo2来画图的话,就必须在shader中将location == 1



0 0
原创粉丝点击