Opengl学习笔记

来源:互联网 发布:油漆测哪些环保数据 编辑:程序博客网 时间:2024/06/06 08:36


简介

OpenGL(全写Open Graphics Library)是个定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像(二维的亦可),是一个功能强大,调用方便的底层图形库。为了使openGL的使用更加简单方便,目前有glewglut两个辅助库可以使用。

绘图方式

绘图方式分为传统绘图方式和现代绘图方式。传统绘图方式中有立即模式和显示列表。现代绘图方式分为顶点数组绘图现代VBO VAO绘图以及结合Shader绘图

顶点数组对象(vertex-array object简称VAOVAO用于存储图形处理器将怎么使用VBO里面的数据,及顶点数据中哪些是坐标、哪些是颜色、哪些是法线等信息的。

顶点缓存对象(Vertex Buffer Objects简称VBOVBO用于存储顶点数据,包括顶点颜色、坐标、法线,以及顶点的indices

Shader是着色器,着 色 器 就 是 使 用OpenGL 着 色 语 言(OpenGL Shading
LanguageGLSL)编写的一个小型函数。GLSL是构成所有 OpenGL着色器的语言,它与C++语言非常类似,尽管 GLSL中的所有特性并不能用于 OpenGL的每个着色阶段。我们可以以字符串的形式传输 GLSL着色器到 OpenGL。从3.1版本开始,固定功能管线从核心模式中去除,因此我们必须使用着色器来完成工作。自己定义着色器,自己装配着色管线。

以下介绍的是VBO绘图,未结合Shader使用。

着色器(Shader)

Shader其实就是一段执行在GPU上的程序,此程序使用OpenGL着色语言来编写它是一个描述顶点或像素特性的简单程序,而不是仅仅承载颜色信息的程序片段。由于绘制简单图元没有使用着色器,以下就不详细介绍了。

绘图原理

OpenGL是使用客户端 -服务端的形式实现的,我们编写的应用程序可以看做客户端,而计算机图形硬件厂商所提供的OpenGL实现可以看做服务端。OpenGL的某些实现允许服务端和客户端在一个网络内的不同计算机上运行。这种情况下,客户端负责提交OpenGL命令,这些 OpenGL命令然后被转换为窗口系统相关的协议,通过共享网络传输到服务端,最终执行并产生图像内容。

最终生成的图像包含了屏幕上绘制的所有像素点。像素(pixel)是显示器上最小的可见单元。计算机系统将所有的像素保存到帧缓存(framebuffer)当中,后者是由图形硬件设备管理的一块独立内存区域,可以直接映射到最终的显示设备上。

环境初始化

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.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.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() 是真正为缓存对象分配(或者重新分配)存储空间的。也就是说,如果新的数据大小比缓存对象当前所分配的存储空间要大,那么缓存对象的大小将被重设以获取更多空间。与之类似,如果新的数据大小比当前所分配的缓存要小,那么缓存对象将会收缩以适应新的大小。因此,虽然我们可以直接在初始化的时候指定缓存对象中的数据,但是这只是一种方便的用法而已,并不一定就是最好的方法

//分配sizeof(vertices)的空间,存放vertices数组

GLfloat vertices[4] = {30,30,100,100};

glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

7.6 启用顶点数组属性

调用函数glVertexAttribPointer()glEnableVertexAttribArray()指定顶点着色器的变量与我们存储在缓存对象中数据的关系。这也就是我们所说的着色管线装配的过程,即将应用程序与着色器之间。

只要在内存中数据是规范组织的(保存在一个连续的数组中,不使用其他基于节点
的容器,比如链表),我们就可以使用glVertexAttribPointer() 告诉 OpenGL直接从内存中获取数据。

//设置顶点属性在第 0 位置可访问的数据值

//每个顶点中需要更新的元素个数为2个

//每个元素的数据类型为 GL_FLOAT

//GL_FALSE表示顶点数据在传递到下一个顶点数组之前不需要进行归一化处理

//两个连续元素之间的偏移字节数为0

//起始位置中的第一组数据值为0

glVertexAttribPointer(0, 2,GL_FLOAT, GL_FALSE, 0, 0);

我们通过调用函数glEnableVertexAttribArray()来启用顶点属性数组,同时将 glVertexAttribPointer()初始化的属性数组指针索引传入这个函数。

//启用顶点属性数组

glEnableVertexAttribArray(0);

7.7 绑定当前渲染的顶点数组对象

//绑定顶点数组对象

glBindVertexArray(VAOs)

7.8 设置线宽及颜色

//设置线宽为2

glLineWidth(2);

//设置颜色为红色,不透明

glColor4f(1, 0, 0, 1);

7.9 绘制

调用 glDrawArrays() 来实现顶点数据向 OpenGL管线的传输,进行指定宏所代表图元的绘制。

//绘制两点线,起始位置为0,一共绘制2个点

glDrawArrays(GL_LINES,0,2);

Main函数调用流程

绘制所有简单图元只需改变draw()函数中实现即可,main函数的调用流程都是一样的。代码如下:

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);  //清除颜色缓冲

reshape(400, 400);

//调用绘制图形的函数

draw();

glFlush();//强制之前的 OpenGL 命令立即执行

glutMainLoop();//无限循环,会负责一直处理窗口和操作系统的用户输入等操作

}

绘制基本图形

9.1 直线

应用上面绘图流程,在蓝色的窗口背景上,从起始像素坐标(30,30)到结束像素坐标(100,100)绘制一条颜色是红色,线宽是2的直线。绘制直线代码如下:

void draw()

{

GLuint VAOs;

glGenVertexArrays(1, &VAOs);  

glBindVertexArray(VAOs);

GLuint Buffers;

glGenBuffers(1, &Buffers);

glBindBuffer(GL_ARRAY_BUFFER,Buffers);

GLfloat vertices[4] = {30,30,100,100};

glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

glVertexAttribPointer(0, 2,GL_FLOAT, GL_FALSE, 0, 0);

glEnableVertexAttribArray(0);

glBindVertexArray(VAOs);

glLineWidth(2);

glColor4f(1, 0, 0, 1);

glDrawArrays(GL_LINES,0,2);

}

main函数中调用draw()函数,执行的结果如下图:

 

9.2 圆形

openGL没有提供绘制圆形的基础方法,所谓圆,其本质是多条直线连接起来的多边形,多边形的边越多,在一定像素下看起来就越圆。只要学会的openGL的绘图流程,绘制圆形的关键就是计算,计算出一个圆形被分割成多条线段后的直线坐标。

应用上面绘图流程,在蓝色的窗口背景上,绘制圆心为(200,200),半径为50颜色是红色,线宽是5的圆形。绘制圆形的代码如下:

const double Pi = 3.141592653;

void draw()

{

//返回 1 个未使用的对象名到VAOs中,用作顶点数组对象

GLuint VAOs;

glGenVertexArrays(1, &VAOs);  

//绑定顶点数组对象 使之作为当前对象

glBindVertexArray(VAOs);

//返回 1 个未使用的对象名到Buffers中,用作顶点缓存对象

GLuint Buffers;

glGenBuffers(1, &Buffers);

//绑定顶点缓存对象 激活为当前可用对象

glBindBuffer(GL_ARRAY_BUFFER,Buffers);

//分配sizeof(vertices)的空间,存放vertices数组

GLfloat vertices[128];

int n = 30,cx = 200, cy = 200, r = 50;

int k = 0;

for (int i = 0;i < n;i++)

{

GLfloat x1 =cos(2 * Pi / n*i);

GLfloat y1 =sin(2 * Pi / n*i);

vertices[k++] =cx + r*x1;

vertices[k++] =cy + r*y1;

}

glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

//设置顶点属性在第 0 位置可访问的数据值

//每个顶点中需要更新的元素个数为2个

//每个元素的数据类型为 GL_FLOAT

//GL_FALSE表示顶点数据在传递到下一个顶点数组之前不需要进行归一化处理

//两个连续元素之间的偏移字节数为0

//起始位置中的第一组数据值为0

glVertexAttribPointer(0, 2,GL_FLOAT, GL_FALSE, 0, 0);

//启用顶点属性数组

glEnableVertexAttribArray(0);

//绑定顶点数组对象

glBindVertexArray(VAOs);

//设置线宽为5

glLineWidth(5);

//设置颜色为红色,不透明

glColor4f(1, 0, 0, 1);

//绘制圆形,起始位置为0,一共绘制n个点

glDrawArrays(GL_LINE_LOOP, 0,n);

}

如果改变函数glDrawArrays()中的参数宏为GL_TRIANGLE_FAN,可以绘制出被填充颜色为红色的圆。

main函数中调用draw函数,执行的结果如下图:

 

 

9.3 矩形

矩形其实就是由四条直线构成的,只需根据传入的矩形左下角起始位置的坐标,以及矩形的宽高计算出矩形四条边的交点坐标,然后调用绘制直线的方法即可。

应用上面绘图流程,在蓝色的窗口背景上,绘制左上角点坐标为(50,50),宽、高分别为100,边为红色,宽度为2的矩形。绘制矩形的代码如下:

void draw()

{

/返回 1 个未使用的对象名到VAOs中,用作顶点数组对象

GLuint VAOs;

glGenVertexArrays(1, &VAOs);  

//绑定顶点数组对象 使之作为当前对象

glBindVertexArray(VAOs);

//返回 1 个未使用的对象名到Buffers中,用作顶点缓存对象

GLuint Buffers;

glGenBuffers(1, &Buffers);

//绑定顶点缓存对象 激活为当前可用对象

glBindBuffer(GL_ARRAY_BUFFER,Buffers);

//分配sizeof(vertices)的空间,存放vertices数组

GLfloat vertices[10];

int ltx = 50,lty = 50, width = 100, height = 100;

GLfloat x2 = (ltx +width);

GLfloat y2 = (lty +height);

int i = 0;

vertices[i++] =ltx;

vertices[i++] =lty;

vertices[i++] =x2;

vertices[i++] =lty;

vertices[i++] =x2;

vertices[i++] =y2;

vertices[i++] =ltx;

vertices[i++] =y2;

vertices[i++] =ltx;

vertices[i++] =lty;

glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

//设置顶点属性在第 0 位置可访问的数据值

//每个顶点中需要更新的元素个数为2个

//每个元素的数据类型为 GL_FLOAT

//GL_FALSE表示顶点数据在传递到下一个顶点数组之前不需要进行归一化处理

//两个连续元素之间的偏移字节数为0

//起始位置中的第一组数据值为0

glVertexAttribPointer(0, 2,GL_FLOAT, GL_FALSE, 0, 0);

//启用顶点属性数组

glEnableVertexAttribArray(0);

//绑定顶点数组对象

glBindVertexArray(VAOs);

//设置线宽为2

glLineWidth(2);

//设置颜色为红色,不透明

glColor4f(1, 0, 0, 1);

//绘制矩形,起始位置为0,一共绘制5个点

glDrawArrays(GL_LINE_LOOP, 0, 5);

}

如果改变函数glDrawArrays()中的参数宏为GL_TRIANGLE_FAN,可以绘制出被填充颜色为红色的矩形。

main函数中调用draw函数,执行的结果如下图:

 

9.4 多边形

多边形是由多条边构成的闭合图形,需要传入所有点的坐标。

应用上面绘图流程,在蓝色的窗口背景上,绘制边为红色,宽度为2的五边形。五边形的顶点坐标见如下代码:

void draw()

{

//返回 1 个未使用的对象名到VAOs中,用作顶点数组对象

GLuint VAOs;

glGenVertexArrays(1, &VAOs);  

//绑定顶点数组对象 使之作为当前对象

glBindVertexArray(VAOs);

//返回 1 个未使用的对象名到Buffers中,用作顶点缓存对象

GLuint Buffers;

glGenBuffers(1, &Buffers);

//绑定顶点缓存对象 激活为当前可用对象

glBindBuffer(GL_ARRAY_BUFFER,Buffers);

//分配sizeof(vertices)的空间,存放vertices数组

GLfloat vertices[10] = {50,50,100,80,200,40,120,200,60,150};

glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

//设置顶点属性在第 0 位置可访问的数据值

//每个顶点中需要更新的元素个数为2个

//每个元素的数据类型为 GL_FLOAT

//GL_FALSE表示顶点数据在传递到下一个顶点数组之前不需要进行归一化处理

//两个连续元素之间的偏移字节数为0

//起始位置中的第一组数据值为0

glVertexAttribPointer(0, 2,GL_FLOAT, GL_FALSE, 0, 0);

//启用顶点属性数组

glEnableVertexAttribArray(0);

//绑定顶点数组对象

glBindVertexArray(VAOs);

//设置线宽为2

glLineWidth(2);

//设置颜色为红色,不透明

glColor4f(1, 0, 0, 1);

//绘制多边形,起始位置为0,一共绘制5个点

glDrawArrays(GL_LINE_LOOP, 0, 5);

}

如果改变函数glDrawArrays()中的参数宏为GL_TRIANGLE_FAN,可以绘制出被填充颜色为红色的多边形。

main函数中调用draw函数,执行的结果如下图:

 

9.5 多点线

多点线与多边形类似,只是起始点做终点坐标不相连接,因此只需复制绘制多边形的函数代码,将glDrawArrays(GL_LINE_LOOP, 0, 5);中的宏改为GL_LINE_STRIP即可。

main函数中调用修改后的draw函数,可以绘制出线宽为2,颜色为红色的五点线,执行的结果如下图:

 

9.6 

弧与多点线的绘制在本质上其实是一样的,openGL本身并没有绘制弧的函数,因此需要将弧分割成多个线段,计算出弧分成多个线段后的每个线段坐标,然后将所有线段相连接。

应用上面绘图流程,在蓝色的窗口背景上,在圆心坐标为(200,200),弧的起始坐标为(200,50),终点坐标为(250,200),逆时针绘制颜色为红色,线宽为2的弧。弧分成30段线段进行绘制,绘制弧的代码如下:

//判断两浮点数是否相等

bool eq(double a,double b)

{

return fabs(a -b) < 0.000001;

}

const double PI = 3.141592653;

//两点坐标求弧度

double toArc(double x1,double y1,double x2,double y2)

{

double dx =x2 - x1;

double dy =y2 - y1;

if (dx == 0 &&dy == 0) return 0;

else if (dy == 0)

{

if (x2 >x1

return 0;

else 

 return PI;

}

else if (dx == 0)

{

if (y2 <y1)

return 1.5*PI;

else

return 0.5*PI;

}

else

{

double l =hypot(dx,dy);

double arc =atan(dy /dx);

if (!eq(x1 +cos(arc)*l,x2, 12))

arc +=PI;

if (arc < 0)

{

arc =arc + 2 * PI;

}

return arc;

}

}

//绘制弧

void draw()

{

  //返回 1 个未使用的对象名到VAOs中,用作顶点数组对象

  GLuint VAOs;

  glGenVertexArrays(1, &VAOs);  

  //绑定顶点数组对象 使之作为当前对象

  glBindVertexArray(VAOs);

  //返回 1 个未使用的对象名到Buffers中,用作顶点缓存对象

  GLuint Buffers;

  glGenBuffers(1, &Buffers);

  //绑定顶点缓存对象 激活为当前可用对象

  glBindBuffer(GL_ARRAY_BUFFER,Buffers);

  int n = 30;

  GLfloat angleTemp =toArc(200, 200, 200, 50);

  GLfloat angleTemp2 =toArc(200, 200, 250, 200);

  GLdouble angle =angleTemp2 - angleTemp;

  GLfloat vertices[128];int k = 0;

  for (int i = 0;i < n;i++)

  {

   GLfloat x1 =cos(angle /n*i +angleTemp);

GLfloat y1 =sin(angle /n*i +angleTemp);

vertices[k++] = 200 + 50*x1;

vertices[k++] = 200 + 50*y1;

}

if (angle < 0)

{

angle +=PI * 2;

}

//分配sizeof(vertices)的空间,存放vertices数组

glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

//设置顶点属性在第 0 位置可访问的数据值

//每个顶点中需要更新的元素个数为2个

//每个元素的数据类型为 GL_FLOAT

//GL_FALSE表示顶点数据在传递到下一个顶点数组之前不需要进行归一化处理

//两个连续元素之间的偏移字节数为0

//起始位置中的第一组数据值为0

glVertexAttribPointer(0, 2,GL_FLOAT, GL_FALSE, 0, 0);

//启用顶点属性数组

glEnableVertexAttribArray(0);

//绑定顶点数组对象

glBindVertexArray(VAOs);

//设置线宽为2

glLineWidth(2);

/设置颜色为红色,不透明

glColor4f(1, 0, 0, 1);

/绘制弧,起始位置为0,一共绘制30个点

glDrawArrays(GL_LINE_STRIP, 0, 30);

}

main函数中调用draw函数,可以绘制弧线,执行的结果如下图:

 

9.7 椭圆

椭圆的绘制也与圆类似,这一类图元的本质都是将曲线分割成多个小线段,然后绘制线段进行连接,分割的线段越多份,绘制的曲线越圆滑。

应用上面绘图流程,在蓝色的窗口背景上,中心点坐标为(200,200),宽为200,高为130的矩形的内切椭圆。椭圆边线颜色为红色,线宽为2。当前示例将椭圆共分割成了宽除以30乘以2份(width/30*2),弧分成30段线段进行绘制,绘制椭圆的代码如下:

void draw()

{

//返回 1 个未使用的对象名到VAOs中,用作顶点数组对象

GLuint VAOs;

glGenVertexArrays(1, &VAOs);  

//绑定顶点数组对象 使之作为当前对象

glBindVertexArray(VAOs);

//返回 1 个未使用的对象名到Buffers中,用作顶点缓存对象

GLuint Buffers;

glGenBuffers(1, &Buffers);

//绑定顶点缓存对象 激活为当前可用对象

glBindBuffer(GL_ARRAY_BUFFER,Buffers);

int cx = 200,cy = 200, width = 200, height = 130;

int bom[128];int j = 0,k = 0;;

GLfloat vertices[256];

for (GLint i =cx - width / 2;i <= cx +width/2; i += width / 30)

{

int a =width / 2;

int b =height / 2;

double a2 =a*a;

double b2 =b*b;

double x_300 = (i -cx)*(i -cx);

double zuo =b2 - (b2*x_300 /a2);

double y_300 =sqrt(zuo);

vertices[k++] =i;

vertices[k++] =y_300 + cy;

bom[j++] =i;

bom[j++] = -y_300 +cy;

}

for (int h =j-1; h >= 0;h-=2)

{

vertices[k++] =bom[h-1];

vertices[k++] =bom[h];

}

//分配sizeof(vertices)的空间,存放vertices数组

glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

//设置顶点属性在第 0 位置可访问的数据值

//每个顶点中需要更新的元素个数为2个

//每个元素的数据类型为 GL_FLOAT

//GL_FALSE表示顶点数据在传递到下一个顶点数组之前不需要进行归一化处理

//两个连续元素之间的偏移字节数为0

//起始位置中的第一组数据值为0

glVertexAttribPointer(0, 2,GL_FLOAT, GL_FALSE, 0, 0);

//启用顶点属性数组

glEnableVertexAttribArray(0);

//绑定顶点数组对象

glBindVertexArray(VAOs);

//设置线宽为2

glLineWidth(2);

//设置颜色为红色,不透明

glColor4f(1, 0, 0, 1);

//绘制椭圆,起始位置为0,一共绘制k/2个点

glDrawArrays(GL_LINE_STRIP, 0,k/2);

}

如果改变函数glDrawArrays()中的参数宏为GL_TRIANGLE_FAN,可以绘制出被填充颜色为红色的椭圆。

main函数中调用draw函数,执行的结果如下图:

 

9.8 曲线

贝塞尔曲线的绘制不像其它曲线(例如:圆、椭圆、弧)那么简单方便,因为贝塞尔曲线的数学计算相对来说比较麻烦,而且对于数学没有那么好的人不太好理解。因此我结合网上资源绘制贝塞尔曲线时采用了传统的绘制方法中的立即模式。

应用上面绘图流程,在蓝色的窗口背景上,颜色为红色,线宽为2的贝塞尔曲线,控制点坐标见下面具体代码。绘制曲线的代码如下:

void draw()

{

GLfloat points[12] = { 100, 100, 0,200, 50,0, 210, 90,0, 300, 320 ,0};

//设置贝塞尔曲线,这个函数其实只需要调用一次,可以放在SetupRC中设置

glMap1f(GL_MAP1_VERTEX_3,//生成的数据类型

0.0f, //u值的下界

30.0f, //u值的上界

3, //顶点在数据中的间隔,x,y,z所以间隔是3

4, //u方向上的阶,即控制点的个数

&points[0]);//指向控制点数据的指针

//设置线宽为2

glLineWidth(2);

//设置颜色为红色,不透明

glColor4f(1, 0, 0, 1);

//必须在绘制顶点之前开启

glEnable(GL_MAP1_VERTEX_3);

//使用画线的方式来连接点

glBegin(GL_LINE_STRIP);

for (int i = 0;i <= 30; i++)

{

glEvalCoord1f((GLfloat)i);

}

glEnd();

}

main函数中调用draw函数,执行的结果如下图:

 

9.9 文字

9.9.1 绘制文字原理

OpenGL本身并没有绘制文字的概念,更没有绘制文字的功能,他只是一个三维绘图的API集和,很多东西都要自己动手去实现。网上介绍的openGL常用绘制文字的方式有3(以下文字来源网络)

第一,将要绘制的文字按照每一个字生成一个小纹理的方式,然后再用将纹理贴到网格

的表面,绘制出来,例如:“博客园-你好”,则会生成6个小纹理,然后生成网格,将纹理贴到网格的表面。优点:每一个字的大小颜色都可选择。缺点:文字多了以后,频繁的切换纹理造成效率低下。OSG中使用了这种方式,效率极差,尤其是在文字更新的情况下。原理如图  博  客  园   -   你  好 。

第二,直接将随绘制的文本字符串生成一个纹理数据(而不是一个文字一个纹理),这样做效率上比第一种要好很多,缺点就是更新的时候要重新构建一个新的纹理。速度上有很大损失。原理如图         -  你  好  。

第三,将所绘制的文字都放到一个较大的纹理上去,然后再纹理上做索引,当绘制的时候,去查表。在将纹理贴到网格上绘制出来,这种方式,速度很快,很多游戏引擎都在使用这种方式,存在的问题绘制的文字多了以后速度会变慢,占用大量的cpu时间,当然对于小的应用已经足够了。原理如下图:

              博客园-你好博客园-你好博客园

              你好博客园-你好博客园-你好博

              博客园-你好博客园-你好博客园

9.9.2 绘制文字流程

绘制文字的方法简单说来就是,调用一系列函数在文字大小的方形区域中,按照初始加载的字体形状,在该方形区域中去掉字体形状之外的部分,那么剩余部分就和开始加载的字体形状是一样的,也就是所谓的openGL绘制出的字体。

 

9.9.3 绘制文字

应用上面绘图流程,在蓝色的窗口背景上,左下角位置在屏幕坐标(100,100),宽高分别为30像素,绘制颜色为红色,内容为Hello!世界的文字。绘制文字的代码如下:

struct xCharTexture1

{

GLuint  m_texID;

wchar_t m_chaID;

int     m_Width;

int     m_Height;

 

int     m_adv_x;

int     m_adv_y;

int     m_delta_x;

int     m_delta_y;

public:

xCharTexture1()

{

m_texID = 0;

m_chaID = 0;

m_Width = 0;

m_Height = 0;

}

};

class xFreeTypeLib1

{

FT_Library m_FT2Lib;

FT_Face    m_FT_Face;

public:

bool load(const char*font_file);

GLuint loadChar(wchar_t ch,int w,int h);

};

 

struct xCharTexture1 g_TexID[65536];

bool xFreeTypeLib1::load(const char*font_file)

{

FT_Library library;

if (FT_Init_FreeType(&library))

return false;

//加载一个字体,取默认的Face,一般为Regualer  

if (FT_New_Face(library,font_file, 0, &m_FT_Face))

return false;

//选择字符表  

FT_Select_Charmap(m_FT_Face,FT_ENCODING_UNICODE);

m_FT_Face->num_fixed_sizes;

return true;

}

GLuint xFreeTypeLib1::loadChar(wchar_t ch,int w,int h)

{

FT_Set_Pixel_Sizes(m_FT_Face,w, h);

if (g_TexID[ch].m_texID)

return g_TexID[ch].m_texID;

///* 装载字形图像到字形槽(将会抹掉先前的字形图像) */

if (FT_Load_Glyph(m_FT_Face,FT_Get_Char_Index(m_FT_Face,ch), FT_LOAD_FORCE_AUTOHINT))

throw std::runtime_error("FT_Load_Glyph failed");

xCharTexture1&charTex = g_TexID[ch];

//得到字模  

FT_Glyph glyph;

//把字形图像从字形槽复制到新的FT_Glyph对象glyph中。这个函数返回一个错误码并且设置glyph。   

if (FT_Get_Glyph(m_FT_Face->glyph, &glyph))

return 0;

//转化成位图  

FT_Render_Glyph(m_FT_Face->glyph,FT_RENDER_MODE_LCD);   

FT_Glyph_To_Bitmap(&glyph,ft_render_mode_normal, 0, 1);

FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;

//取道位图数据  

FT_Bitmap&bitmap = bitmap_glyph->bitmap;

//把位图数据拷贝自己定义的数据区里.这样旧可以画到需要的东西上面了。  

int width =bitmap.width;

int height =bitmap.rows;

 

m_FT_Face->size->metrics.y_ppem;      //伸缩距离到设备空间  

m_FT_Face->glyph->metrics.horiAdvance;  //水平文本排列  

 

charTex.m_Width =width;

charTex.m_Height =height;

 

charTex.m_adv_x =m_FT_Face->glyph->advance.x / 64.0f;  //步进宽度  

charTex.m_adv_y =m_FT_Face->size->metrics.y_ppem;        

charTex.m_delta_x = (float)bitmap_glyph->left;   //left:字形原点(0,0)到字形位图最左边象素的水平距离.它以整数象素的形式表示。   

charTex.m_delta_y = (float)bitmap_glyph->top;   //Top: 类似于字形槽的bitmap_top字段。  

glGenTextures(1, &charTex.m_texID);

glBindTexture(GL_TEXTURE_2D,charTex.m_texID);

char*pBuf = new char[width *height * 4];

for (int j = 0;j < height;j++)

{

for (int i = 0;i < width;i++)

{

unsigned char _vl = (i >=bitmap.width ||j >= bitmap.rows) ? 0 :bitmap.buffer[i +bitmap.width*j];

pBuf[(4 *i + (height -j - 1) * width * 4)] = 0xFF;

pBuf[(4 *i + (height -j - 1) * width * 4) + 1] = 0xFF;

pBuf[(4 *i + (height -j - 1) * width * 4) + 2] = 0xFF;

pBuf[(4 *i + (height -j - 1) * width * 4) + 3] = _vl;

}

}

 

glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, width, height, 0, GL_RGBA,GL_UNSIGNED_BYTE, pBuf);  //指定一个二维的纹理图片  

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP);                            

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexEnvi(GL_TEXTURE_2D,GL_TEXTURE_ENV_MODE, GL_REPLACE);//纹理进行混合                       

delete[]pBuf;

return charTex.m_chaID;

}

xFreeTypeLib1 g_FreeTypeLib1;

xCharTexture1*getTextChar(wchar_t ch,int w,int h)

{

g_FreeTypeLib1.loadChar(ch,w, h);

return &g_TexID[ch];

}

 

void DrawTex(wchar_t*_strText, int x, int y, int maxW, int h, int ww, int hh)

{

int sx =x;

int sy =y;

int maxH =h;

size_t nLen =wcslen(_strText);

     for (int i = 0;i < nLen;i++)

{

if (_strText[i] =='/n')

{

sx =x; sy +=maxH + 12;

continue;

}

xCharTexture1*pCharTex = getTextChar(_strText[i],ww, hh);

glBindTexture(GL_TEXTURE_2D,pCharTex->m_texID);//绑定到目标纹理                           

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glEnable(GL_BLEND);  //打开或关闭OpenGL的特殊功能  

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);    //特殊的像素算法  

int w =pCharTex->m_Width;

int h =pCharTex->m_Height;

int ch_x =sx + pCharTex->m_delta_x;

int ch_y =sy - pCharTex->m_delta_y;

if (maxH <h) maxH =h;

glBegin(GL_QUADS);      // 定义一个或一组原始的顶点  

{

glTexCoord2f(0.0f, 1.0f);glVertex3f(ch_x,ch_y, 1.0f);

glTexCoord2f(1.0f, 1.0f);glVertex3f(ch_x +w, ch_y, 1.0f);

glTexCoord2f(1.0f, 0.0f);glVertex3f(ch_x +w, ch_y +h, 1.0f);

glTexCoord2f(0.0f, 0.0f);glVertex3f(ch_x,ch_y + h, 1.0f);

}

glEnd();

sx +=pCharTex->m_adv_x;

if (sx >x + maxW)

{

sx =x; sy +=maxH + 12;

}

}

}

bool InitFont(char*font)

{

glShadeModel(GL_SMOOTH |GL_FLAT);        //选择平直或平滑着色  

glEnable(GL_COLOR_MATERIAL_FACE);

glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);//使一个材质色彩指向当前的色彩  

bool b =g_FreeTypeLib1.load(font);

glDisable(GL_CULL_FACE);

return b;

}

void draw()

{

char*font = "c://windows//fonts//simhei.ttf";

bool bFont =InitFont(font);//初始化字体

if (bFont ==false)

{

cout <<"字体未初始化" << endl;

return;

}

glEnable(GL_TEXTURE_2D);

glColor4f(1,0,0,1);

//绘制文字

//100,100 是文字左下角像素坐标

//400,400是窗口宽高

//30,30是单个文字绘制像素大小,宽高

DrawTex(L"Hello!世界", 100, 100, 400, 400, 30, 30);

glDisable(GL_TEXTURE_2D);

}

main函数中调用修改后的draw函数,绘制文字,效果如下图所致:

 

10 资源释放

10.1 回收顶点数组对象

我们完成对顶点数组对象的操作之后,是可以调用glDeleteVertexArrays(GLsizei n,GLuint *arrays) 将它释放,这样所有的名称可以再次用作顶点数组。如果绑定的顶点数组已经被删除,那么当前绑定的顶点数组对象被重设为 0(类似执行了glBindBuffer() 函数,并且输入参数为 0),而默认的顶点数组会变成当前对象。在 arrays当中未使用的名称都会被释放,但是当前顶点数组的状态不会发生任何变化。

//删除1个存在VAOs中的顶点数组对象

glDeleteVertexArrays(1, &VAOs);

10.2 回收顶点缓存对象

所有的缓存对象都可以使用glDeleteBuffers() 直接释放。删除 n个保存在 buffers 数组中的缓存对象。被释放的缓存对象可以重用(例如,使用 glGenBuffers())。如果删除的缓存对象已经被绑定,那么该对象的所有绑定将会重置为默认的缓存对象,即相当于用 0作为参数执行 glBindBuffer()  的结果。如果试图删除不存在的缓存对象,或者缓存对象为 0,那么将忽略该操作(不会产生错误)。

//删除1个存在Buffers中的顶点缓存对象

glDeleteBuffers(1, &Buffers);