`
mmdev
  • 浏览: 12914987 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Qt下的OpenGL 编程(7)顶点数组和显示列表

 
阅读更多


一、提要
        OpenGL作为一个高性能的图形接口,性能能肯定是放在第一位的了,
现在的移动平台也是OpenGL ES,这对性能的要求就更高了,
今天我们要接触到的这两个东西—顶点数组和显示列表都是用于实现高性能绘图的手段。
         下一篇教程我打算去做一个一个3D漫游的例子,会用到今天的技术。

二、顶点数组
        在之前的几篇教程中,有时候绘制一个图形需要很多次函数调用才能完成。
首先调用一次glBegin(),然后为每个点调用一次函数,最后还要调用glEnd().中间
如果还要设置颜色或是法线什么的,函数的调用次数还要增加,这给系统带来了很大的
开销,影响了程序的性能。

        OpenGL提供了一些顶点数组函数,允许只用少数几个数组指定大量的与顶点相关的数据,
把数据放在顶点数组中可以提高应用程序的性能还可一减少函数调用的次数,从而提高性能。
另外,使用顶点数组还可以避免共享顶点的冗余处理。
        使用顶点数组对几何图元进行渲染的3个步骤:
       1)激活数组,存储顶点坐标、RGBA颜色等等;
       2)装载数组
       3)利用数组绘制图形

       首先用一个简单的例子来演示一下,用顶点数组绘制带颜色的三角形。
       首先在initializeGL()中设置参数:

       void NeHeWidget::initializeGL()
       {

           // 启用阴影平滑
           glShadeModel( GL_SMOOTH );
           // 黑色背景
           glClearColor( 0.0, 0.0, 0.0, 0.0 );
           // 设置深度缓存
           glClearDepth( 1.0 );
           // 启用深度测试
           glEnable( GL_DEPTH_TEST );
           // 所作深度测试的类型
           glDepthFunc( GL_LEQUAL );
           // 告诉系统对透视进行修正
           glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

           glClearColor(0.0, 0.0, 0.0, 0.0);
           // 启用顶点数组
           glEnableClientState(GL_VERTEX_ARRAY);
           // 颜色数组也需要启用
           glEnableClientState(GL_COLOR_ARRAY);
           // 默认就是此参数,可忽略,为了明确说明特意指定
           glShadeModel(GL_SHADE_MODEL);
           // 顶点数组数据
           static GLfloat fVertices[] = {    -0.5, -0.5,
                                             0.5, -0.5,
                                             0.5,  0.5,
                                            };
           // 颜色数组
           static GLfloat fColor[] = { 1.0, 0.0, 0.0,
                                       0.0, 1.0, 0.0,
                                       0.0, 0.0, 1.0,
                                       };
           // 指定顶点数组数据
           glVertexPointer(2, GL_FLOAT, 0, fVertices);
           //指定颜色数组
           glColorPointer(3, GL_FLOAT, 0, fColor);
       }


       接着在paintGL()中绘制图形:


void NeHeWidget::paintGL()
{

    // 清除屏幕和深度缓存
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glLoadIdentity();
    //移到屏幕的左半部分,并且将视图推入屏幕背后足够的距离以便我们可以看见全部的场景
    glTranslatef(0.0f,0.0f,zoom);


    glBegin(GL_TRIANGLES);
    glArrayElement(0);
    glArrayElement(1);
    glArrayElement(2);
    glEnd();

}
        可以看到,我们得到的效果和一个个定义顶点,一个个定义颜色是一样的。


           下面解释其中的几个函数:

           void glEnableClientState(GLenum array)

           用于制动需要启用的数组,例子中GL_VERTEX_ARRAY表示顶点数组,GL_COLOR_ARRAY表示颜色数组。开启一种顶点数组都必须调用glEnableClientState来激活.

          glDisableClientState(GLenum array)

        用于关闭相应的数组。
  

       void glVertexPointer(	GLint  	size,GLenum  	type,GLsizei  	stride,const GLvoid *  	pointer);

        指定需要访问的空间的坐标数据,作用是把一数组与图形的顶点关联在一起。
  glColorPointer意思相类似。
         glArrayElement则根据顶点数组来调用相应的函数,每次只调用1个顶点。

跨距

        如果将两个数组合并在一起,那么坐标和颜色的数据就在一个数组了,现在代码修改如下:
        static GLfloat data[]={
            -0.5, -0.5,1.0, 0.0, 0.0,
            0.5, -0.5, 0.0, 1.0, 0.0,
            0.5,  0.5, 0.0, 0.0, 1.0,
        };

        glVertexPointer(2,GL_FLOAT,5*sizeof(GLfloat),&data[0]);
        glColorPointer(3,GL_FLOAT,5*sizeof(GLfloat),&data[2]);


        这里用到了跨距的概念,第3个参数指定跨距,如颜色,从数组的每组数据的第3个开始取数据,然后跨5个,坐标顶点则从数据每组数据的第1个开始取数据并跨5个.

        注意:由于跨距需要计算数据类型,所以数组的数据类型需要相同.
        最后得到的效果与之前的一样。



解引用数组
        下面要用到的是glDrawElements,学名叫解引用数组,作用类似于循环调用glArrayElement,但需要定义一个索引的数组。有了它,我们只需要一句函数调用就可以把图形绘制出来了,继续修改代码。

        只需修改paintGL()中的代码,将glBegin()和glEnd()还有它们之间的代码换成:
        //定义索引数组
        GLubyte index[]= {0,1,2,3} ;
        //解引用数组
            glDrawElements(GL_TRIANGLES,3,GL_UNSIGNED_BYTE,index);

        接下来,我们来利用解引用数组绘制一个立方体。

    //正方体绘制
    static GLfloat data[]={
        -1.0, -1.0, -1.0 , 0.0, 0.0, 0.0 ,
        1.0, -1.0, -1.0 ,0.0, 0.0, 1.0,
        1.0, 1.0, -1.0 ,0.0, 1.0, 0.0 ,
        -1.0, 1.0,-1.0 ,0.0,1.0, 1.0,

        -1.0, -1.0, 1.0 ,1.0, 0.0, 0.0,
        1.0, -1.0, 1.0 ,  1.0, 0.0, 1.0,
        1.0, 1.0, 1.0 ,1.0,1.0, 0.0,
        -1.0, 1.0, 1.0 ,1.0, 1.0, 1.0 ,
    };
    //指定顶点数据
    glVertexPointer(3,GL_FLOAT,6*sizeof(GLfloat),&data[0]);
    //指定颜色数据
    glColorPointer(3,GL_FLOAT,6*sizeof(GLfloat),&data[2]);
     //指定索引序列
static const GLint face_lists[][4]=
             {
             0, 3, 2, 1,
             6, 5, 1, 2,
             3, 0, 4, 7,
             3, 7, 6, 2,
             0, 4, 5, 1,
             7, 4, 5, 6,
             };//注意每个面绘制的顺序,背面采用顺时针方向。

              //绘制图形
              glDrawElements( GL_QUADS, 24, GL_UNSIGNED_INT, face_lists );


最后结果如下:


        我们在定义出数组之后,只用了一条语句便画出了一个立方体,而不是像之前那样定义24次顶点,同时定义24次颜色。


        glArrayElement击败了glVertex*,但是glDrawElements又比glArrayElement更高。glDrawElements使用的时候将一连串glArrayElement需要使用的数据放在一个数组中(此例中是byRectIndices),然后通过一个函数调用一次指定。注意啊,glDrawElements自带glBegin及glEnd效果,不再需要它们。(事实上,在OpenGL中命名中带Draw的一般都不需要glBegin和glEnd)程序运行的效果与glVertexArrayWithColor例子运行效果相同。

          需要提一下的还有glMultiDrawElements,它是glDrawElements的升级版,一条语句相当于多条glArrayElements,接口有点麻烦,使用方法可以参考文档。


混合数组
        这个有点类似上面说到的跨距的使用了,也是将不同的顶点数组放在同一个数组中,然后用相应的方法把
它解析出来,这里要用到的是glInterleavedArrays,函数将会根据参数,激活各种顶点数组,并存储顶点。
        修改代码:

static GLfloat data[]={
    -0.5, -0.5,1.0, 0.0, 0.0,
    0.5, -0.5, 0.0, 1.0, 0.0,
    0.5,  0.5, 0.0, 0.0, 1.0,
};
glInterleavedArrays(GL_C3F_V3F,0,data);
glDrawArrays(GL_LINES,0,4);


        对应于相应的参数,就可以实现相应的图形,感觉这个比跨距的使用更简单一些,代码也会更加清晰。


总结
        OpenGL提供了一系列更加高效的函数以完成对效率要求苛刻的任务,我们将逐一介绍,会发现,使用难度越来越高,适用范围越来越窄,但是函数调用越来越少,功能越来越强大,事实上,函数调用少并不是OpenGL这样设计的唯一理由,越是这样同时处理多个数据的接口,因为一个接口掌握的信息越多,那么也就越能更多的对其进行优化。


三、显示列表

            显示列表可以用来存储OpenGL函数,供以后执行。如果需要多次重绘同一个几何图形,或者如果有一些需要多次调用的用于更改状态的函数,把这些函数存储在显示列表中是一个很好的思路。
            为了实际操作一下,我们先在我们的项目中添加glut库。
            在.pro文件中添加
            LIBS += -lglut


         简单介绍一下glut库
GLUT(英文全写:OpenGL Utility Toolkit)是一个处理OpenGL程式的工具库,负责处理和底层操作系统的呼叫以及I/O,并包括了以下常见的功能:
定义以及控制视窗
侦测并处理键盘及鼠标的事件
以一个函数呼叫绘制某些常用的立体图形,例如长方体、球、以及犹他茶壶(实心或只有骨架,如glutWireTeapot())
提供了简单选单列的实现。
         *注意:在使用glut函数之前一定要对其进行和初始化,在man.cpp的main函数中加入glutInit(&argc, argv);


创建显示列表
  OpenGL提供类似于绘制图元的结构即glBegin()与glEnd()的形式创建显示列表,其相应的函数为:

void glNewList(GLuint list,GLenum mode);
  说明一个显示列表的开始,其后的OpenGL函数存入显示列表中,直至调用结束表的函数(见下面)。参数list是一个正整数,它标志唯一的显示列表。参数mode的可能值有GL_COMPILE和GL_COMPILE_AND_EXECUTE。若要使后面的函数语句只存入而不执行,则用GL_COMPILE;若要使后面的函数语句存入表中且按瞬时方式执行一次,则用GL_COMPILE_AND_EXECUTE。


void glEndList(void);
  标志显示列表的结束。


执行显示列表
    在建立显示列表以后就可以调用执行显示列表的函数来执行它,并且允许在程序中多次执行同一显示列表,同时也可以与其它函数的瞬时方式混合使用。显示列表执行的函数形式如下:

  void glCallList(GLuint list);
  执行显示列表。参数list指定被执行的显示列表。显示列表中的函数语句按它们被存放的顺序依次执行;若list没有定义,则不会产生任何事情。下面举出一个应用显示列表的简单例子:


首先是没有用显示列表的状态:
//绘制100个茶壶
 glColor3f( 0.0, 1.0,1.0 );
for(int i=0;i<10;i++)
{
    for(int j=0;j<10;j++)
    {
        glTranslatef(j*0.5f,0.0f,0.0f);
        glPushMatrix();
        glutWireTeapot(0.2);
        glPopMatrix();
    }
   glTranslatef(0.0f,-0.4f,0.0f);
}

结果会在屏幕上绘制100个茶壶。



下面用显示列表来实现:
在initializeGL()中添加:
glNewList (teapotList, GL_COMPILE);
glutWireTeapot(0.2);
glEndList ();


创建了一个teapotList的列表,接着我们在循环中替换相应的代码:
for(int i=0;i<10;i++)
{
    for(int j=0;j<10;j++)
    {
        glPushMatrix();
        glTranslatef(j*0.5f,0.0f,0.0f);
        //glutWireTeapot(0.2);
        glCallList (teapotList);
       glPopMatrix();

    }
   glTranslatef(0.0f,-0.4f,0.0f);
}

实际运行发现渲染的速度会快一点点(感觉是心理作用),可能是小的场景差别不是很大,有兴趣的同学可以测一下fps,结果就会明晰了。

四.参考资料

1.OpenGL Reference Manual》,OpenGL参考手册

2.《OpenGL编程指南》(《OpenGL Programming Guide》),Dave Shreiner,Mason Woo,Jackie Neider,Tom Davis著,徐波译,机械工业出版社

3. 《win32 OpenGL编程 》 一个大牛的博客 http://blog.csdn.net/vagrxie/article/category/628716/3
4.             《OpenGL函数思考 》   里面有很多OpenGL函数的通俗解释     http://blog.csdn.net/shuaihj



分享到:
评论

相关推荐

    Qt下openGL编程

    这是Qt结合openGL的小程序,功能比较简单,仅供新手学习。本程序开发环境:Qt Creator4.0.2+Qt5.7 注:如果程序运行出错,那是因为缺少glut库。可以参考下面链接解决:...

    glwidget_QT_用QT的opengl显示图片_

    用QT的opengl显示图片,一个类的定义文件。

    Qt与Matlab混合编程中mwArray数组使用详解

    演示Qt 5.9与Matlab 2017b混合编程中,用于传递数据的mwArray数组的使用方法,包括数组维数设置、传入数据、读取返回数据、字符串型数据等。博文地址 https://blog.csdn.net/HongAndYi/article/details/79477031

    Qt+OpenGL实现三维地形显示

    Qt+OpenGL实现三维地形显示,数字地图使用图片形式存储

    QtOpenGL文字显示

    Qt版的OpenGLDemo,其中包含透视投影,纹理贴图,文字显示,反锯齿。

    QT控件数组创建的一种方法

    使用QTfor语句创建控件数组(button[i]),方便大量控件调用,本程序以Qpushbutton为例,语言简洁

    Qt+opengl加载各种类型的3D模型,.glb .obj 等;Qt+opengl加载各种类型的3D模型,.glb .obj

    Qt+opengl加载各种类型的3D模型,.glb .obj 等;Qt+opengl加载各种类型的3D模型,.glb .obj 等;Qt+opengl加载各种类型的3D模型,.glb .obj 等;Qt+opengl加载各种类型的3D模型,.glb .obj 等;Qt+opengl加载各种...

    OpenGL+qt抗锯齿OpenGL+qt抗锯齿

    OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿OpenGL+qt抗锯齿...

    Linux下用QT开发OpenGL教程

    Linux下用QT开发OpenGL教程,浅显易懂!

    QT+OPENGL 多线程测试

    本人原创,浅显易懂,QT+OPENGL多线程,采用2D纹理贴图的方式,多线程进行纹理拷贝和渲染。

    QT--OPENGL-串口-雷达点云图显示

    1:利用 OpenGL 进行点云及直线的显示(同时使用两个着色器); 2:可以实现串口的自动识别及操作; 3:雷达图像的显示(可以实现点云数据及点云距离的同比例缩放及显示); 4:在OpenGL显示部分,相机矩阵与代码中...

    Qt5OpenGl.dll

    qt打包提示确实Qt5OpenGL.dll,可以用这个,或者下载威龙触摸屏程序安装后也可以得到

    Qt读取txt文件的内容,保存到数组,并将其显示出来

    Qt读取txt文件的内容,保存到数组,并将其显示出来,采用QtextStream读取文件,利用QVector建立数组,最后在QLineEdit上显示出来。QTextEdit显示方法同理。

    qt+opengl实现帧缓冲

    qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl实现帧缓冲qt+opengl...

    Qt+opengl实现爆破物体

    Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl实现爆破物体Qt+opengl...

    QT+OpenGL+学习基于QT开发OpenGL应用

    基于QT 实现了大部分LearnOpenGL的例子,可以作为学习QT、OpenGL的参考

    Qt_OpenGL3D图形的绘制和旋转

    如果我们要绘制一个3D的四棱锥只需要绘制这5个面即可,绘制的方法和前一篇文章OpenGL_Qt学习笔记之_03(平面图形的着色和旋转)的相同。只不过这里的顶点坐标是3维的,所以图像深度那一维不一定为0。因此我们可以事先...

    Qt+opengl实现分屏效果

    Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl实现分屏效果Qt+opengl...

    基于Qt的OpenGL编程并能实现相应按钮点击事件

    这是我的毕设题目中写的软件,基本功能是输入数据,就会在opengl框中绘制图像,用到了matlab,opengl,是基于qt开发的(vs2013中的qt插件),里面都是调用到的操作opengl的方法,也是比较新的,网上有很多老的方法...

Global site tag (gtag.js) - Google Analytics