在本文中我们将会接触到OpenGl的扩展库GLEW( OpenGL Extension Wrangler Library),GLEW可以帮助我们处理OpenGl中繁琐的扩展管理。一旦初始化后可以查询当前平台中所有可用的扩展,能够动态的加载它们并通过一个单独的头文件来方便的使用这些扩展。
本教程中我们第一次使用的顶点缓存对象(VBOs),正如名字所暗示的,顶点缓存对象是用来存放顶点的。你能够想象到的3D世界中的所有对象,无论是怪物、城堡还是一个简单的旋转立方体,在计算机中都是通过一组顶点构成的。顶点缓存是將顶点加载到GPU最有效的方式,它是显存中的缓存数据,因此GPU能够使用较短的时间读取它们。
这一篇教程和下一篇是该系列教程中唯独的两篇使用固定渲染管道取代可编程管道。事实上这两篇教程中都没有任何变换操作,仅仅是將数据写入管道中。管道的概念将会在以后的教程介绍,现在需要掌握的是数据在到达光栅器之前,可见顶点的X/Y/Z坐标范围在[-1.0,1.0]之间就够了。光栅器会將这些坐标映射到屏幕中(例如屏幕的在水平方向是1024像素,那么-1.0对应屏幕的第0个像素,1.0对应屏幕的第1023个像素)。最后光栅器根据调用绘图函数时指定的绘图模式(例如GL_POINTS、GL_LINES)来绘制基本图形。由于我们没有为管线绑定任何着色器,所以我们的顶点并没有发生任何变换。这意味着我们只需要给顶点坐标一个在[-1.0,1.0]范围内的值,确保它能显示出来即可。如果將点的X/Y坐标值指定为0,该点就会显示在屏幕中央。
GLEW下载地址:http://glew.sourceforge.net/
在上一篇文章中笔者提供的库文件压缩包中包含GLEW的库文件和头文件,使用方法请自行参考。
1.在上一篇文章创建的解决方案中新建控制台项目。
2.在项目上点击右键,打开属性页在vc++目录中的包含目录中添加$(SolutionDir)Include
路径;在库目录中添加$(SolutionDir)Lib
路径。在链接器->输入->附加依赖项中添加freeglut.lib和glew32.lib。
/* Copyright 2010 Etay Meiri This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Tutorial 02 - Hello dot! */
#include "stdafx.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
GLuint VBO;
//创建顶点结构体,用于表示OpenGL中的顶点
struct Vector3f
{
float x;
float y;
float z;
Vector3f(){}
Vector3f(float _x, float _y, float _z)
{
x = _x;
y = _y;
z = _z;
}
};
static void RenderSceneCB()
{
glClear(GL_COLOR_BUFFER_BIT);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_POINTS, 0, 1);
glDisableVertexAttribArray(0);
glutSwapBuffers();
}
static void InitializeGlutCallbacks()
{
glutDisplayFunc(RenderSceneCB);
}
static void CreateVertexBuffer()
{
Vector3f Vertices[1];
Vertices[0] = Vector3f(0.0f, 0.0f, 0.0f);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}
int _tmain(int argc, _TCHAR* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA);
glutInitWindowSize(1024, 768);
glutInitWindowPosition(100, 100);
glutCreateWindow("Tutorial 02");
InitializeGlutCallbacks();
// Must be done after glut is initialized!
GLenum res = glewInit();
if (res != GLEW_OK) {
fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
return 1;
}
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
CreateVertexBuffer();
glutMainLoop();
return 0;
}
#include <GL/glew.h>
这里包含GLEW头文件,为了能正确链接GLEW库文件,在链接器->输入->附加依赖项中添加glew32.lib
struct Vector3f
{
float x;
float y;
float z;
Vector3f(){}
Vector3f(float _x, float _y, float _z)
{
x = _x;
y = _y;
z = _z;
}
};
创建顶点结构体,用来表示OpenGl中的顶点。
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 结构数组,把X/Y/Z初始化为0,确保该点能显示在屏幕中央。
GLuint VBO;
定义一个GLuint类型的全局变量用来存放顶点缓存对象的句柄,你將会看到大多数OpenGl对象都是通过GLuint类型的变量来访问的。
glGenBuffers(1, &VBO);
OpenGl定义了一些glGen*函数来产生各种类型的对象。这些函数通常有两个参数,第一个参数用来指定你想创建对象的个数,第二个参数是GLuints类型数组地址,用来存放为你分配的驱动句柄(请确保数组足够大来处理你的请求)。glGenBuffers用于产生缓冲区对象,之后再次调用该函数产生的句柄不会和先前的相同除非你有调用过glDeleteBuffers函数。需要注意的是此时你并没有说明如何使用该缓冲区,所以它被认为是“通用”的,这是为下一个函数的调用做准备工作。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
OpenGl采用十分独特的方式使用句柄。在许多API中,句柄只是简单的传递给相关的函数,具体做什么操作由函数的其他参数指定。在OpenGl中我们將句柄和目标名绑定(GL_ARRAY_BUFFER为目标名,VBO为句柄),接着就会根据该目标名执行相应的命令。GL_ARRAY_BUFFER 表示该缓存区中包含一个顶点数组。另外一个有用的目标名为GL_ELEMENT_ARRAY_BUFFER,表示缓存区中数据为顶点在另一个缓存区的索引。其他目标名将会在以后的文章中见到。
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
在绑定我们的对象之后,我们需要向缓冲区对象中填充数据,该函数的参数分别是:目标名GL_ARRAY_BUFFER(需要和glBindBuffer函数绑定的一致),数据的大小,顶点数组的地址,数据使用模式标志。因为我们不会改变缓冲区内容,所以这里指定GL_STATIC_DRAW,和GL_STATIC_DRAW相反的是GL_DYNAMIC_DRAW。
glEnableVertexAttribArray(0);
在之后的着色器教程中,你会看到在着色器中使用顶点的属性,有一个索引映射到它们,使你能创建c/c++程序中的数据和着色程序中属性名之间的绑定。此外你还必须把每个顶点属性索引设置为允许状态,在本教程中我们没有使用着色器,顶点位置已经加载到缓存区,所以调用该函数將顶点属性索引设置为0。这个调用是必须的,否则管线无法访问缓存中的数据。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
在这里我们再次绑定我们的缓冲准备进行绘制调用,在这个小程序我们只有一个顶点缓冲,但是在更复杂的程序中会用到更多的缓存来存储各种模型,还必须更新将要使用的缓冲对象的管线状态。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
通过这个函数的调用告诉管线如何处理缓冲区中的数据,第一个参数指定属性索引,在这个例子中,我们知道它默认为0,但当我们使用着色器时,需要显式设置在着色程序中的索引值。第二个参数指定每个顶点属性的组件数量,必须为1、2、3或者4(本例中为3个,分别为X/Y/Z)。第三个参数是每个组件的数据类型。接下来的参数指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE),这里我们设置为GL_FALSE。第五个参数指定连续顶点属性之间的偏移量,当只有一个属性或者数据紧密排列在一起时传入0。假如我们有一个包含位置和法线的结构体数组,该参数应该传入结构体所占字节数。最后一个参数非常有用,指定一个指针,指向数组中第一个顶点属性的第一个组件。
glDrawArrays(GL_POINTS, 0, 1);
最后,我们调用该函数绘制几何图形。到目前为止我们看到的所有命令都很重要,但是它们只是为绘图命令做准备。直到此函数的调用,GPU才真正开始工作。结合第一个参数指定的绘制方式將图形渲染在屏幕上。
OpenGl为适应不同的情况提供了几种绘制方式。通常可以分为两类–顺序绘制和索引绘制。顺序绘制非常简单,GPU会遍历顶点缓冲区,然后根据指定的绘制方式生成几何图形,如果绘制方式指定为GL_TRIANGLES,第0~2个顶点构成第一个三角形, 第3~5 个顶点构成第二个三角形。如果你想要相同的顶点出现在不止一个三角形中,需要在顶点缓存中指定两次,这是很浪费空间的。
索引绘制相对复杂一些,会涉及到另一个缓存称为索引缓存。索引缓存中存放的是顶点在顶点缓存中的索引。GPU会扫描索引缓存,第0~2个索引指向的顶点构成第一个三角形,第3~5个索引指向的顶点构成第二个三角形。如果你需要两个三角形公用顶点,只需在索引缓存中指定。索引绘制方式在游戏中是非常常见的,因为大多数3D模型都是由无数个三角形构成的,有很多三角形都是共用顶点。
在这个案例中,我们只是简单的调用glDrawArrays绘制一个点。采用顺序绘制方式,所以不涉及到索引缓存。我们指定绘制方式为GL_POINTS意味着所有的顶点作为一个单独的点,不进行连线。第二个参数指定需绘制的第一个顶点的索引。在这个案例中我们需要从缓存区的第一个顶点点开始,所以参数指定为0。最后一个参数指定绘制顶点的数量(这里指定为1,表示只绘制一个点)。
glDisableVertexAttribArray(0);
当顶点属性不再使用时需禁用它,这是很好的编程习惯。
源码下载:http://download.csdn.net/detail/rongbo_j/8579603