您的位置:首页 > 运维架构

OpengGL第八版的第一个例子

2015-10-01 17:31 363 查看
不得不说,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]);//当前操作的是vbo1
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));//1代表vbo1
glBindBuffer(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 == 1
glFlush();
}
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 core
void 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


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息