2画个三角形

绘制三角形时,要用到顶点缓存(VBO)的知识。个人感觉,这部分已经是GL比较中级的知识了。所以这里不做详细解释,也不拿VBO和顶点数组(VA)和显示列表作对比。我觉得,像GL这种实践性很强的技术,还是先动手把效果实现了再说。《OpenGL超级宝典》之所以提供GLTools,就是想先把复杂的原理屏蔽掉,让初学者能快速实现效果,随着程序写的越多,有些原理自然而然就理解了。否则,一开始就灌输那么多原理的东西,但连个最基本的三角形都画不出来,我想大多数人都会很快失去兴趣,尤其是像DirectXOpenGL这种偏底层的技术。

所以,这里我只简单说一下VBO的作用。VBO全名Vertex Buffer Object——顶点缓存对象。它的作用就是存储顶点的一系列属性,比如顶点的位置,颜色,法线,纹理坐标等等。GL在绘制的过程中,就是根据VBO的数据把图形画出来。

2.1顶点,快到碗里来~

2.1.1创建VBO


既然,VBO是存储顶点的属性,那首先得有碗吧。

所以,第一步就是创建VBO

voidglGenBuffers(GLsizein,GLuint*buffers);

n:要产生的VBO的数量

buffers:存储VBO的句柄。

PSGLsizei=intGLuint= unsignedint

有人会问,干嘛这么麻烦,直接用int不就好了。OpenGL是跨平台的API,所以要兼顾各个平台,所以自己重新定义了类型。这个知道就行,建议养成用GL专有类型的好习惯。


2.1.2绑定VBO


voidglBindBuffer(GLenum  target,GLuint    buffer);

绑定一个缓冲区对象。

target:可能取值是:GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, or GL_PIXEL_UNPACK_BUFFER.

我们现在主要使用GL_ARRAY_BUFFER GL_ELEMENT_ARRAY_BUFFER

GL_ARRAY_BUFFER用于保存之前说的,顶点的各种属性

GL_ELEMENT_ARRAY_BUFFER用于保存顶点索引,这个在以后用顶点索引模型绘制时会用到。

当进行绑定之后,以前的绑定就失效了。

在绑定的同时,也将该缓存设置成当前缓存。GL对缓存的一切操作,都是对当前缓存有效。


PS这么做的原因,是因为OpenGL采用状态机的模式。glBindBuffer就是讲GL的缓存模式切换到当前缓存。可能,习惯面向对象的人不习惯这种模式。GL是一种C语言API,所以GL中并没有真正的对象改变。GL中的对象,实际上就是一个int类型的标识符,对于熟悉Windows编程的朋友来说,也就是所谓的句柄。所以,GL要对使用或者改变某对象,采用的就是绑定该对象句柄的方式。而不是,C++的真正类,可以直接调用对象的成员方法。


2.1.3VBO填充数据


选择好要操作的VBO之后,就要为VBO装数据,这里的数据,可以是顶点位置,顶点颜色等等

voidglBufferData(GLenum target,GLsizeiptr size,constGLvoid * data,GLenum usage);

target和之前glBindBuffer中的target含义是一样的。

size填充数据的字节数,注意是字节数,不是个数

data:具体的数据。通常用数组

usage:缓存区的用途。当不确定用途时,使用GL_DYNAMIC_DRAW是一个比较安全的值。


2.1.4开始画了

准备工作都做好了,现在就可以开画了。

我们画的三角形要到了顶点的两个属性:位置和颜色。

所以用到了两个VBO,我们要创建两个VBO

首先定义两个全局变量

init()方法内,添加如下代码


GLuint vbo[2];
glGenBuffers(2, vbo);
posBuf = vbo[0];
colorBuf = vbo[1];


然后,就是绘制三角形的代码了。

首先在display内,在glClear()glSwapBuffer()之间,添加如下代码。


srand(time(0));
                                                                               
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
                                                                               
GLfloat verts[] = {
    200, 100, 0,
    600, 100, 0,
    400, 400, 0
};
                                                                               
GLfloat colors[] = {
    (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
    (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
    (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f
};
                                                                               
glBindBuffer(GL_ARRAY_BUFFER, posBuf);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glVertexPointer(3, GL_FLOAT, 0, 0);
                                                                               
glBindBuffer(GL_ARRAY_BUFFER, colorBuf);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
glColorPointer(3, GL_FLOAT, 0, 0);
                                                                               
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
    glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);


#1:为随机数设置种子,种子为当前时间。关于C++随机数的问题,问Google~~,作为一名合格的码农,Google是必备技能。


#3 - #4:设置当前为模型视图模式,并重置模型试图矩阵。这里不详细解释,这些步骤都是必须的。等后面讲到GLSL时,自然而然就明白了。


#6 - #10:定义三角形的三个顶点。为了方面阅读,特意分成3行。3个顶点分别为(200, 100, 0), (600, 100, 0), (400, 400, 0)。注意哦,这个例子中,我们使用的是二维坐标系,原点在左下角,宽是窗口的宽度(800),高是窗口的高度(600)。至于,怎么设置,稍后分析


#12 - #16:定义三角形的颜色。这下我们srand()就派上用场了。所谓会变色,其实就是每秒随机生成一种颜色。GL设置颜色是使用浮点类型的,RGBA的范围都是0.0f~1.0f


#18 - #20:这里就是关键代码了。

1.glBindBuffer(GL_ARRAY_BUFFER, posBuf);首先,先选择要操作的VBO。我们先操作,存储顶点位置的混存。

2.glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

然后,为该缓冲区填充数据。注意第二个参数是字节大小,所以可以使用sizeof获取。

注意,sizeof有个陷阱哦。因为sizeof()verts是在同一方法体内,所以sizeof(verts)取得的是verts数组的字节大小。但是,如果verts是通过方法形参传递给sizeof,那sizeof(verts)取得的是float类型指针的大小,不是数组的大小。因为,数组在作为方法实参时,已经退化成指针了。所以,这时候我们就要改成 N *sizeof(float), N是数组的个数

3.glVertexPointer(3, GL_FLOAT, 0, 0);设置顶点位置的指针。说明白点,就是让GL知道,如果想要顶点位置,就从当前缓存区里取就可以了。

  • 参数1:顶点位置的大小。因为是3维坐标(x,y,z),自然取3

  • 参数2:顶点位置的类型。GL_FlOAT,这个很明白了

  • 参数3:每个顶点属性的间隔。简单地说,我们顶点位置和颜色都是分开设置的,利用不同的数组。所以,每个顶点位置都是连续的。比如(200, 100, 0),后面就是下个顶点(600, 100, 0),顶点位置之间的间隔为0

  • 参数4:这个参数可能有点迷糊人。虽然它的参数类型是指针类型,但在使用VBO时,实际上指的是偏移位置。我们要用到VBO内的顶点位置,所以设为0

#22 - #24:设置颜色的方法,和设置顶点位置的方法是一样的。所以就不重复了。唯一的区别,就是告诉GL,这个缓存colorBuf保存的是顶点颜色。

glColorPointer(3, GL_FLOAT, 0, nullptr);


#26 - #27:开启对应的顶点属性。GL对于顶点的各个属性,默认都是关闭的。

  • GL_VERTEX_ARRAY:顶点位置

  • GL_COLOR_ARRAY:顶点颜色

注意到方法名有个Client吗?Client是客户端的意思(+_+没有人不知道吧~~)。这个例子,我们用的还是固定管线的做法,但是抛弃了glBegin/glEndglVertexglNormal。那玩意实在是太旧了。Client指的就是当前的GL程序,我们在GL程序里设置好顶点属性后,再将属性传给管线,管线就是服务器了。因为是固定管线,我们不可以干涉管线的工作。也因为这样,我们才会调用glVertexPointer/glColorPointer来告诉GL,哪个是顶点位置,哪个是顶点颜色。等到后面,可编程管线时。我们就能操作,管线的工作了。到那时,所有的顶点属性,通通调用glVertexAttribPointer来指定。当然,这是后话。


#28:这句就是真正把三角形画出来了。

glDrawArrays(GL_TRIANGLES, 0, 3);

GL有多重绘制模型,包括点,线,三角形,三角形扇等等。这里,我们使用GL_TRIANGLES,来指定GL画三角形。


#29 - #30:恢复GL的默认设置。养成好习惯,绘制完成后,恢复GL的设置。


大功告成,来试试吧~~

p_w_picpath

说好的三角形呢?

OpenGL了解的朋友,可能已经知道问题所在。到目前为止,都没有出现glViewport,gluOrtho,gluPerspective,导致没有设置GL的绘制区域和投影模式。现在我们就来设置。


void reshape(int width, int height){
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
                                                                      
   glViewport(0, 00, width, height);
   gluOrtho2D(0, width, 0, height);
}

#2:选择当前模式为投影模式。


#3:重置当前的投影模式。


#5glViewport:设置GL的绘制区域,这里我们设置为整个窗口的大小。


#6gluOrtho2D:设置GL的投影模式为正交投影。关于正交投影和透视投影,老方法,Google大把。


实际上,之前我们设定的二维坐标,就是这句代码起的作用。

打完收工。这下再来看看

p_w_picpath

三角形是有了,颜色也有了。but~~说好的变色呢。~~      

其实新的颜色已经产生了,但是没有画出来而已,我们只需在display()中的glSwapBuffers下面添加一句就可以了


glutPostRedisplay();


这句代码就是告诉glut,重新画一帧。

终于操蛋的出来了,不容易啊。

再来几张~~

p_w_picpath

p_w_picpath

附上全部源码

#define GLEW_STATIC
#include <GL/glew.h>
#include <GL/freeglut.h>
#include <iostream>
#include <ctime>
                                                         
GLuint posBuf, colorBuf;
                                                         
void init(){
    glClearColor(0, 0, 0 ,1);
    glEnable(GL_DEPTH_TEST);
                                                         
    GLuint vbo[2];
    glGenBuffers(2, vbo);
    posBuf = vbo[0];
    colorBuf = vbo[1];
}
                                                         
void display(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    srand(time(0));
                                                         
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
                                                         
    GLfloat verts[] = {
        200, 100, 0,
        600, 100, 0,
        400, 400, 0
    };
                                                         
    GLfloat colors[] = {
        (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
        (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f,
        (rand() % 256) / 255.0f, (rand() % 256) / 255.0f, (rand() % 256) / 255.0f
    };
                                                         
    glBindBuffer(GL_ARRAY_BUFFER, posBuf);
    glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
    glVertexPointer(3, GL_FLOAT, 0, 0);
                                                         
    glBindBuffer(GL_ARRAY_BUFFER, colorBuf);
    glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
    glColorPointer(3, GL_FLOAT, 0, 0);
                                                         
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
        glDrawArrays(GL_TRIANGLES, 0, 3);
    glDisableClientState(GL_COLOR_ARRAY);
    glEnableClientState(GL_VERTEX_ARRAY);
                                                         
    glutSwapBuffers();
    glutPostRedisplay();
}
                                                         
void reshape(int width, int height){
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0, 00, width, height);
    gluOrtho2D(0, width, 0, height);
}
                                                         
void keyboard(unsigned char key, int x, int y){
                                                         
}
                                                         
void keyboardUp(unsigned char key, int x, int y){
                                                         
}
                                                         
void specialKey(int key, int x, int y){
                                                         
}
                                                         
void specialKeyUp(int key, int x, int y){
                                                         
}
                                                         
int main(int argc, char **argv){
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("会变色的三角形");
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutKeyboardUpFunc(keyboardUp);
    glutSpecialFunc(specialKey);
    glutSpecialUpFunc(specialKeyUp);
    glutDisplayFunc(display);
    glFlush();
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    init();
    glutMainLoop();
    return 0;
}


本回到此为止,欲知后事如何,且听下回分解~~


PS:本篇作为一篇试用博文,最想看到就是效果如何。第一次写这么详细的文章,前前后后花了5个小时。其实,我也是接触OpenGL的一点皮毛,作为一个初学者,还是能体会学习GL的难度。从刚开始接触NEHE教程,到红皮书,到国内出的杂七杂八的GL书籍,再到最后的蓝宝书。我也走了不少弯路,为了让以后的刚刚接触GL的初学者不要踏进之前我踩过的坑,于是萌生了把自己的学习心得记录下来的想法。

写的过程中,自己肯定有理解的偏差,如果有任何不对的地方,敬请指正~~。如果对本文的写作风格有任何建议的话,尽管提~~

谢谢!