1 简介
OpenGL(全写Open Graphics Library)是个定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像(二维的亦可),是一个功能强大,调用方便的底层图形库。为了使openGL的使用更加简单方便,目前有glew与glut两个辅助库可以使用。
2 绘图方式
绘图方式分为传统绘图方式和现代绘图方式。传统绘图方式中有立即模式和显示列表。现代绘图方式分为顶点数组绘图、现代VBO VAO绘图以及结合Shader绘图。
顶点数组对象(vertex-array object)简称VAO,VAO是用于存储图形处理器将怎么使用VBO里面的数据,及顶点数据中哪些是坐标、哪些是颜色、哪些是法线等信息的。
顶点缓存对象(Vertex Buffer Objects)简称VBO,VBO用于存储顶点数据,包括顶点颜色、坐标、法线,以及顶点的indices。
Shader是着色器,着 色 器 就 是 使 用 OpenGL 着 色 语 言(OpenGL Shading
Language, GLSL)编写的一个小型函数。GLSL 是构成所有 OpenGL 着色器的语言,它与C++ 语言非常类似,尽管 GLSL 中的所有特性并不能用于 OpenGL 的每个着色阶段。我们可以以字符串的形式传输 GLSL 着色器到 OpenGL。从3.1版本开始,固定功能管线从核心模式中去除,因此我们必须使用着色器来完成工作。自己定义着色器,自己装配着色管线。
以下介绍的是VBO绘图,未结合Shader使用。
3 着色器(Shader)
Shader其实就是一段执行在GPU上的程序,此程序使用OpenGL着色语言来编写。它是一个描述顶点或像素特性的简单程序,而不是仅仅承载颜色信息的程序片段。由于绘制简单图元没有使用着色器,以下就不详细介绍了。
4 绘图原理
OpenGL是使用客户端 - 服务端的形式实现的,我们编写的应用程序可以看做客户端,而计算机图形硬件厂商所提供的OpenGL实现可以看做服务端。OpenGL的某些实现允许服务端和客户端在一个网络内的不同计算机上运行。这种情况下,客户端负责提交OpenGL 命令,这些 OpenGL命令然后被转换为窗口系统相关的协议,通过共享网络传输到服务端,最终执行并产生图像内容。
最终生成的图像包含了屏幕上绘制的所有像素点。像素(pixel)是显示器上最小的可见单元。计算机系统将所有的像素保存到帧缓存(framebuffer)当中,后者是由图形硬件设备管理的一块独立内存区域,可以直接映射到最终的显示设备上。
5 环境初始化
5.1 初始化窗口
5.1.1 默认窗口颜色
GLUT使得我们创建openGL的应用程序更加简单,帮助openGL创建及管理窗口。
int main(int argc, char** argv)
{
glutInit(&argc, argv); //初始化glut库
glutInitWindowPosition(10, 10); //窗口左上角起始位置
glutInitWindowSize(400, 400); //窗口大小
glutCreateWindow("openGL"); //窗口名称
if (glewInit()) //初始化glew库
{
std::cout << "Unable to initialize GLEW ... exiting" << endl;
exit(EXIT_FAILURE);
}
glutMainLoop(); //无限循环,会负责一直处理窗口和操作系统的用户输入等操作
}
执行main函数后,创建的窗口如下图所示:
5.1.2 修改窗口背景颜色
如果想改变窗口背景颜色,可以在main函数的 glutMainLoop()函数之上调用两个函数glClearColor()和glClear(),glClearColor()函数负责设置背景清除颜色,设置适当的宏glClear()函数可以使用背景颜色清除颜色缓冲区,如果不设置背景清除颜色,openGL默认清除颜色是黑色。
以下是使用蓝色为清除颜色,清除背景缓冲区。
int main(int argc, char** argv)
{
glutInit(&argc, argv); //初始化glut库
glutInitWindowPosition(10, 10); //窗口左上角起始位置
glutInitWindowSize(400, 400); //窗口大小
glutCreateWindow("openGL"); //窗口名称
if (glewInit()) //初始化glew库
{
std::cout << "Unable to initialize GLEW ... exiting" << endl;
exit(EXIT_FAILURE);
}
glClearColor(0, 0, 1, 1); //设置清除颜色为蓝色
glClear(GL_COLOR_BUFFER_BIT); //清除颜色缓冲
glFlush(); //强制之前的 OpenGL 命令立即执行
glutMainLoop();//无限循环,会负责一直处理窗口和操作系统的用户输入等操作
}
执行当前main函数后,创建的窗口如下图所示:
5.2 设置显示区域
openGL中的默认坐标系是世界坐标系,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。面对屏幕,右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。长度单位这样来定:窗口范围按此单位恰好是(-1,-1)到(1,1),即屏幕左下角坐标为(-1,-1),右上角坐标为(1,1)。
为了方便绘制时传入参数是当前屏幕像素坐标。因此要设置显示区域的视窗与窗口视窗一样大小,屏幕坐上角为(0,0)点。
void reshape(int w, int h)
{
// Reset the coordinate system before modifying
glMatrixMode(GL_PROJECTION); //定义矩阵
glLoadIdentity(); //用恒等矩阵替换当前矩阵
// Set the viewport to be the entire window
glViewport(0, 0, w, h); //设置视窗
glOrtho(0, w, h, 0, -100, 200); //用垂直矩阵与当前矩阵相乘
}
6 设置属性函数
6.1 宽度
设置线宽,调用函数glLineWidth(),线宽如果不设置默认为1,最大线宽为10,如果设置比10大的参数,openGL也会默认线宽为最大值10。
glLineWidth(2);
6.2 颜色
设置线的颜色,调用函数glColor4f(),函数的4个参数分别是rgba,范围在0 ~ 1之间,glColor4f(1, 1, 1, 1)是白色,不透明。
glColor4f(1, 0, 0, 1);
7 绘图一般流程
7.1 获得顶点数组对象
调用glGen*系列的函数glGenVertexArrays()获得一些openGL中随机分配的未使用的顶点数组对象(VAO)的名称供我们使用。返回的名字可以用来分配更多的缓存对象,并且它们已经使用未初始化的顶点数组集合的默认状态进行了数值的初始化。这里的名称类似 C 语言中的一个指针变量,我们必须分配内存并且用名称引用它之后,名称才有意义。
//返回 1 个未使用的对象名到VAOs中,用作顶点数组对象
GLuint VAOs;
glGenVertexArrays(1, &VAOs);
7.2 绑定顶点数组对象
在 OpenGL 中,给顶点数组对象分配内存的机制叫做绑定对象(bind an object),它是通过一系列 glBind* 形式的 OpenGL 函数集合去实现的。我们通过调用glBindVertexArray();函数创建并且绑定了一个顶点数组对象。
当我们第一次绑定对象时,OpenGL 内部会分配这个对象所需的内存并且将它作为当前对象,即所有后继的操作都会作用于这个被绑定的对象,例如,这里的顶点数组对象的状态就会被后面执行的代码所改变。在第一次调用 glBind*() 函数之后,新创建的对象都会初始化为其默认状态,而我们通常需要一些额外的初始化工作来确保这个对象可用。
//绑定顶点数组对象 使之作为当前对象
glBindVertexArray(VAOs);
7.3 获取顶点缓存对象
顶点数组对象负责保存一系列顶点的数据。这些数据保存到缓存对象当中,并且由当
前绑定的顶点数组对象管理。我们只有一种顶点数组对象类型,但是却有很多种类型的
对象,并且其中一部分对象并不负责处理顶点数据。
顶点缓存对象的初始化过程与顶点数组对象的创建过程类似,不过需要有向缓存中添
加数据的一个过程。首先,我们需要创建顶点缓存对象的名称。我们调用的还是 glGen* 形式的函数,即glGenBuffers(),返回 n 个当前未使用的缓存对象名称,这些名称不一定是连续的整型数据。 这里返回的名称只用于分配其他缓存对象,它们在绑定之后只会记录一个可用的状态。
//返回 1 个未使用的对象名到Buffers中,用作顶点缓存对象
GLuint Buffers;
glGenBuffers(1, &Buffers);
7.4 绑定顶点缓存对象
当分配缓存的名称之后,就可以调用 glBindBuffer() 来绑定它们了。由于 OpenGL 中有很多种不同类型的缓存对象,因此绑定一个缓存时,需要指定它所对应的类型。在这个例
子中,由于是将顶点数据保存到缓存当中,因此使用 GL_ARRAY_BUFFER 类型。缓存对
象的类型现在共有 8 种,分别用于不同的 OpenGL 功能实现。绑定顶点缓存对象之后才是激活当前顶点缓存对象。
//绑定顶点缓存对象 激活为当前可用对象
glBindBuffer(GL_ARRAY_BUFFER, Buffers);
7.5 加载顶点数据
初始化顶点缓存对象之后,我们需要把顶点数据从对象传输到缓存对象当中。这一步
是通过 glBufferData() 例程完成的,它主要有两个任务:分配顶点数据所需的存储空间,然后将数据从应用程序的数组中拷贝到 OpenGL 服务端的内存中。
要特别注意的是,glBufferData() 是真正为缓存对象分配(或者重新分配)存储空间的。也就是说,如果新的数据大小比缓存对象当前所分配的存储空间要大,那么缓存对象的大小将被重设以获取更多空间。与之类似,如果新的数据大小比当前所分配的缓存要小,那么缓存对象将会收缩以适应新的大小。因此,虽然我们可以直接在初始化的时候指定缓存对象中的数据,但是这只是一种方便的用法而已&#x