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

OpenGL step by step - tutorial_2 "hello dot"

2015-12-02 10:32 323 查看
这是我们第一次接触glew,the OpenGL Extension Wrangler Library。glew帮你处理那些让人头痛的OpenGL扩展。一但初始了glew,它将查询可应用到你正使用的开发平台上的一切有效扩展,动态的加载它们,一切的接口都通过这一个头文件提供。

这也是我们第一次使用顶点缓存对象(VBOs,vertex buffer objects)。意如其名,VBOs是用来存储顶点的。在可视化的3D场景中的对象,无论这个对象是怪兽,城堡还是一个简单的旋转立方体,都是由一组连接在一起的顶点组成的。VBOs是用来向GPU加载顶点的最有效的方式。VBOs是显存内的缓存区(they are buffers that can be stored in video memory),GPU访问它们是最快的,因此首推这种方法。

本篇和下一篇是这个系列唯一的使用固定管线而不是可编程管线的教程。事实上这两篇内都没有出现任何转换操作。我们只不过是依据数据通过这个管线的方式按顺序实现。下一借将会详细讲解管线,现在你只需要知道在光栅化(在屏幕坐标系绘制点、线、三角形)之前,这些可见顶点的xyz坐标范围是[-1.0 , 1.0]就够了。光栅化将该坐标投影到屏幕坐标(例如,如果屏幕宽度1024,那么x坐标-1.0对应像素0,x坐标1.0对应像素1023)。最后,光栅化按照绘制函数指定的拓扑方式绘制图元。因为我们还没有给管线绑定shader,所以顶点还没有经过转换。这就是说,为了能最终看到绘制效果,我们定义顶点坐标值时要在[-1.0
, 1.0]之间取值。那么,我们给xy赋值0就是把这个点放在了这两个轴的中心点——即屏幕中央。

<pre name="code" class="cpp">#pragma comment(lib,"glew32.lib")
#include<stdio.h>
#include<glew.h>
//在这里我们添加了glew头文件。如果你还要添加其他头文件,那请切记要把其他头文件放在此文件下面,因为其他扩展文件的编译会以它为依据(可以试一下与下面的freeglut换行)。
#include<freeglut.h>
#include"math3d.h"
//我们的自定义数据结构,例如现在我们要用到数组存储顶点。随着深入,我们也会不断扩充此文件。

GLuint VBO;
//我们声明一个GLuint类型的全局变量用来存储顶点缓存对象的句柄。
//之后你还会看到大多数OpenGL对象都是通过一个GLuint类型的变量来访问的。
void reDisplay(){
glClear(GL_COLOR_BUFFER_BIT);
glEnableVertexAttribArray(0);
//在shader教程中,shader中使用的顶点属性(vertex attributes)(例如位置、法线等属性)都有一个索引映射到它们,通过这个你就可以把C/C++程序中的数据和shader中的属性名关联起来。
//额外的要求就是你要enable每个定点属性的索引。
//虽然在本例中还未用到shader,但是我们加载到buffer中的顶点位置数据被视为固定管线(当不存在shader是固定管线才可用)中索引编号为0的顶点属性。
//你必须enable每个顶点属性,否则这些数据将不会被管线访问到。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//这里我们再一次绑定我们的buffer,做好调用绘制的准备。
//在这个小程序里,我们只是用了一个顶点buffer,所以每一帧都绑定一次确实有点多余了。但是,在一个更复杂的程序中会用多个buffer存储你的多种模型,并且
//你必须设置你想要使用的buffer来更新管线的状态。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
//这个函数告诉管线应该如何解释读取buffer内的数据。
//第一个参数指定了属性的编号。在我们的这个例子中,我们知道它是0,但是当我们使用shader时要么明确设置要么查询出结果。
//第二个参数指定这个属性内包含的元素个数(3 for xyz).
//第三个参数指定属性内每个元素的数据类型。
//第四个参数指示我们的属性在管线中使用之前是否需要规格化。在我们的这个例子中使用的就是规格化数据[-1.0 ,1.0],所以不需要改变。
//第五个参数指示buffer中相邻两个属性实例间隔的bytes数。当这里只有一种属性(例如这里只有定点位置),并且数据是紧凑排列的,我们设置这个值为0。但是如果这个数组中的属性是位置和法线间隔排列的,这个值就会是(6*4=24)。
//最后一个参数在上一个例子(位置和法线间隔排列)中是很重要的。我们要指定这个偏移量,好让管线找到我们要使用的属性。例如,要使用位置属性,那偏移量就是0;要使用法线属性,那偏移量就是3*4=12。
glDrawArrays(GL_POINTS, 0, 1);
//最后,我们要绘制这个几何图形了!
//到目前,我们见过的命令都是十分重要的,但是,它们也都是在为这个绘制命令做准备。
//从这条命令开始,GPU才真正的开始工作。它将会结合这个draw函数的参数和之前设置的状态在屏幕上绘制这个点。

//OpenGL提供了多种绘制方式,每一种对应不同的实例。大体上可分为两类——顺序绘制和索引绘制。
//顺序绘制很简单。GPU遍历顶点buffer,按照draw call中指定的拓扑方式逐个绘制这些顶点。例如,你指定GL_TRIANGLES方式,那么,顶点0-2绘制第一个三角形,顶点3-5绘制第二个......。如果你希望同一个顶点被多个三角形共用,
//那你就要在这个顶点buffer中指定这个顶点多次,而且还得按照绘制顺序排列。这也是非常浪费空间的。
//索引绘制更复杂一点,并且需要包含另一个叫做索引buffer(index buffer)的buffer。这个index buffer存储的是vertex buffer中顶点的索引值。GPU浏览这个索引buffer,以一个类似的方式按索引顺序绘制那些三角形。这是你要是想
//多个三角形共用顶点,在索引buffer中多写几次这个索引就行了,并不会增加顶点buffer的大小。索引绘制多用在游戏中,因为大多数模型的表面都是三角形表示的,这些连在一起的三角形有很多共用顶点。

//在这个教程中,我们使用了最简单的绘制函数——glDrawArrays。它是一个顺序绘制函数,所以不需要索引buffer。我们指定拓扑格式为points,这就意味着每一个顶点数据代表一个独立的点。第二个参数指明从索引为几的顶点开始绘制。
//如果在同一个buffer中存储了多个模型数据,那选择绘制哪个就依据它相对于0的偏移量了。最后一个参数指明一共要绘制几个顶点。
glDisableVertexAttribArray(0);
//这是一个好的习惯,当你不需要立即使用该顶点属性是就disable它。如果当shader已经不再使用它了,可它还是enable状态,这就是自找麻烦了。

glutSwapBuffers();
}
int main(int argc, char** argv){
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(1024, 768);
glutInitWindowPosition(100,100);
glutCreateWindow("hello dot");

GLenum res = glewInit();
if (res != GLEW_OK){
fprintf(stderr,"Error:'%s'\n",glewGetErrorString(res));
return 1;
}
//在这里,我们初始化了glew并检测是否出错。当然,glew的初始化一定要在glut初始化之后进行。
Vector3f vertices[1];
vertices[0] = Vector3f(0.0f,0.0f,0.0f);
//创建一个Vector3f类型数组,xyz值为0.0。所以,该点最后将显示在屏幕中央。
glGenBuffers(1, &VBO);
//OpenGL定义了很多这样的glGen*函数,用来生成不同类型的对象。这些gen函数通常有两个参数——第一个指定了你想创建几个对象,
//第二个是GLuint类型数组的首地址,用来储存驱动分配给你的句柄(要确定第二个参数的大小足够存储你申请的句柄数!)。之后其他
//针对此函数的调用将不会产生相同的对象句柄,除非你已使用glDeleteBuffers清除了上一个buffer。
//需要注意的是,在此阶段你不需要指出该buffers的功能,它们将被视为“泛型”。指定功能是下一个函数的事情。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//OpenGL都是通过一种独特的方式来使用句柄。在其他的APIs中,句柄就是简单的传递到任何相关的函数,然后对该句柄进行操作。
//但是在OpenGL中,我们先把这个句柄绑定到一个目标名上,然后对这个目标执行命令操作。
//这些命令将一直影响这个被绑定的句柄,直到另一个句柄被绑定到该目标名,或者上面这个函数绑定0作为句柄。
//目标名GL_ARRAY_BUFFER的意思是:这个buffer存储一个顶点数组。
//另一个目标名是GL_ELEMENT_ARRAY_BUFFER,它存储的是另一个buffer中顶点的编号。
//当然还有其他目标名,我们会在以后看到。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//对象绑定后,我就要使用数据填充它。在这个函数中,我们使用了目标名(我们上一步绑定的那个),以bytes为单位的数据大小,顶点数组的地址,以及一个用来说明该数据的使用方式的flag。
//因为我们没有打算去更改这个buffer的内容,所以指定使用方式GL_STATIC_DRAW。与其相对应的是GL_DYNAMIC_DRAW。the driver根据它去优化启动(比如决定该在内存的什么位置存放该buffer)。
glPointSize(5);
glColor3f(1.0f,0.0f,0.0f);

glutDisplayFunc(reDisplay);

glClearColor(0.0f,0.0f,0.0f,0.0f);
glutMainLoop();
return 0;
}



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