背景知识
这是我们第一个遇到GLEW,它是OpenGL的扩展库。GLEW可以帮你处理头痛的问题。一旦他被初始化,它需要所有的平台的扩展包,动态加载它,只需要一个简单的头文件就能轻松访问。
在本节你会看到如何使用顶点缓冲对象(VBO)。正像名字暗含的意思,它使用来存储顶点的。你尽量可视化的存在于3D世界中的物体有:怪物、城堡或者简单的旋转的立方体,而这些物体都是通过连接一系列顶点构建的。VBO能够很高效的加载顶点到GPU。他们是缓冲,可以存储在视频内存中,提供最短的GPU访问时间,所以他们被强烈的建议使用。
本节以及下一节是本系列教程唯一使用固定渲染管线的章节。这两节都没有包含任何的变换。我们仅仅使用的传入到管线中的数据。完整的学习管线只是将会在下面的教程中介绍到。但是现在只要知道在到达光栅化器之前,所有的顶点都有自己的x、y、z坐标,且都在[-1.0,1.0]之间就足够了。光栅化器把这些坐标映射到屏幕空间,比如屏幕的宽度是1024,那么x=-1.0就映射到0,而x=1.0则被映射到1023。最终,光栅化器画一些基本图元,依据的拓扑关系是定义在draw call中。由于我们没有绑定恩和的shader到管线,我们的顶点没有进行任何的变换。这意味着我们仅仅需要给他们一个值,此值在-1.0到1.0之间,就能够被可视化。事实上,把x和y都置为0,他们是坐标轴的中心,也对应着屏幕的中心。
代码注释:
#include
这里我么包含了头文件GLEW。如果你包含了其他的OpenGL的头文件,那么在包含其他头文件之前,要先包含这个头文件,注意顺序。
#include "math_3d.h"
在这个教程中,我们开始使用有用的结构体比如vector。
GLenum res = glewInit();
if (res != GLEW_OK)
{
fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
return 1;
}
这里我们先初始化GLEW,然后检测是否出错。这个必须在GLUT初始化之后做。
Vector3f Vertices[1];
Vertices[0] = Vector3f(0.0f, 0.0f, 0.0f);
我们创建了Vector3f结构体的数组,这个在math_3d.h中定义,然后把它初始化为0。此点将会出现在屏幕的中间位置。
GLuint VBO;
我们声明了一个全局的vbo变量。我们后面会看到很多的类型都是通过GLuint来引用。
glGenBuffers(1, &VBO);
OpenGL定义了几个glGen*函数,他们用以创建不同类型的对象。他们通常接收两个参数——第一个参数定义了你想创建的对象的个数。第二个是数组的地址,这个是GLuints是分配给你的地址。确保你开辟的数组能够容纳你的需求。除非你使用glDeleteBuffers函数,否则再次调用这个函数会返回不同的对下引用。注意,此时你还没有指定你讲用这个缓冲来做什么,所以他被视为通用的。这个是下一个函数的工作:
glBindBuffer(GL_ARRAY_BUFFER, VBO);
Opengl有特定的唯一的方式来使用句柄。在很多的API中,句柄是只是简单的传递给任何相关的函数,然后动作是在句柄上其作用的。在OpenGL中,我们给句柄绑定一个目标名,然后再这个目标上执行命令。这些命令影响了绑定的句柄,直到另外一个目标重新绑定这个句柄,或者你将上面的句柄置为0。这里目标是GL_ARRAY_BUFFER,它意味着缓冲将会包含一个数组的点。另外一个有用的目标是GL_ELEMENT_ARRAY_BUFFER,它的意思是此buffer包含顶点的索引。其他的目标同样可以使用,我们将会在后面看到。
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
在绑定我们的对下之后,我们可以填充数据。上面的调用获取了目标名(这个再上面的绑定功能类似),数据的大小使用字节表示,顶点数组的地址,还有一个标志,此标志解释了如何使用这个数据。由于我们不打算改变缓冲中的内容,我们把它定义为GL_STATIC_DRAW。相反的操作是改变缓冲中的内容,我们定义为GL_DYNAMIC_DRAW。这个标志指示告诉OpenGL,在遇到这个标记的时候,要好好想想如何优化。
glEnableVertexAttribArray(0);
在shaders教程中,我们会看到在shader如何使用顶点属性,如位置、法线等。这些属性都会有一个索引作为映射。这样你可以在C/C++程序和shader中的属性名之间建立一一的映射。除此之外,你必须开启顶点属性索引。在本教程中,我们没有使用任何的shader,但是顶点位置我们也已经加载到缓冲中,然后这个顶点属性的索引在固定关系中被指定为0(在没有任何shader的时候,这个0索引的属性变成了可用状态)。你必须开启每个顶点属性使其可用,否则在关系中这些数据不可被访问。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
这里我们又再次绑定缓冲,因为我们准备绘制了。这个小程序仅仅只有一个顶点缓冲,在每帧都调用,这个有点冗余。但是在更复杂的程序中,将会有很多缓冲来储存你的各种各样的模型,你必须要实时更新你使用的buffer相关的关系的状态。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
这个调用告诉关系如何解释存储在缓冲中的数据。第一个参数指定了属性的索引。在我们的例子中,我们知道0是默认的索引,但是当我们使用shader之后,我们需要显示定义这个索引或者查询它。第二个参数是表示属性的分量个数,这里是顶点所以分量有三个(x、y和z)。第三个参数是每个分量的数据类型。第四个参数表示我们是否想将属性在送给管线使用之前进行规格化。在我们的例子中,我们不希望,所以为false。第五个参数成为步长,表示两个相邻属性之间的有多个少个字节。当只有一个属性的时候(这里是顶点位置),这个属性之间是紧密排布的,所以其步长为0。如果我们有一个数组结构,里面包含了位置和法线,每个属性都是3个float的vector。那么这个结构体的大小为64=24。最后一个参数,用来告诉关系从何处开始找我们的属性。比如在包含顶点和法线的结构体中,顶点应该从0开始寻找,而法线应该从43=12出开始寻找。
glDrawArrays(GL_POINTS, 0, 1);
这里还是要详细介绍下如何配置glew。
1/第一步到http://glew.sourceforge.net/
下载glew的源代码,然后进行编译。
解压之后,到目录下:build下的vc12,虽然我使用的是vs2017,但是可以用它打开此.sln,后面会遇到其他的问题,我们再详细解决。
2/打开这个工程文件,然后会出现这个对话框:
我们可以选择和自己vs匹配的skd版本。这里我们选择的是10.0.14393.0
然后我们要编译的是x64位的版本的lib以及dll,所以要先设置x64。
3/然后编译会报错:
无法打开包括文件: “ctype.h”: No such file or directory
此时我们要修改项目的包含目录,四个都要设置:
在C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\ucrt
中包含这些缺失的文件。
设置完毕后再次编译还是报错:fatal error LNK1104: 无法打开文件“ucrtd.lib”
我们再把库包含进其中的三个项目,上面的三个报错分别对应的项目是glew_shared/visualinfo/glewinfo
这些都设置好之后,编译成功了。
4/到目录:D:\software\glew-2.1.0\lib\Debug\x64
找到了两个.lib的文件:glew32d.lib以及glew32sd.lib文件。
把这两个文件拷贝到:C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\lib
然后,我们再到文件夹:D:\software\glew-2.1.0\bin\Debug\x64
找到glew32d.dll拷贝到:C:\Windows\System32
其实这里我之前是拷贝到:C:\Windows\SysWOW64
下的,但其实这个拷贝到哪里都无所谓,关键是看项目使用哪个目录。我们这里拷贝到C:\Windows\System32下,后面再介绍为什么这么做。
ok,所以的准备工作做好了,然后就是写测试代码了。
我们到:https://github.com/triplepointfive/ogldev/tree/master/tutorial01
把其中的main.cpp的内容,粘贴到我们的新建的项目中,运行程序:
此时我们要添加一个代码,#pragma comment( lib, “glew32d.lib” ),使其引用静态库。
#include "stdafx.h"
#include
#include
#include
#include "math_3d.h"
#pragma comment( lib, "glew32d.lib" )
GLuint VBO;
……
一个白色点出现了。
这里我们还要提下为什么,我的dll能找到,原因是:我们的环境变量Path中包含了寻找dll的路径:
这就是我们为什么把dll直接拷贝到C:\Windows\System32中的原因了。你可以拷贝到其他地方,但是要保证项目能够找到就可以。
over!