QT+openGL

来源:互联网 发布:电子仿真软件列表 编辑:程序博客网 时间:2024/05/23 13:34
转自:http://chuckgao.blogbus.com/tag/openGL/
Chapter 1 openGL
      我决定第一章从最近的工作写起。openGL是什么?我不太能回答这个问题,不过我知道我打算用它来做些什么。我想要做一个简单的导航系统,而我打算用openGL绘制我的三维导航。其实,这对于我来说事件十分复杂的工作,而且我给自己定下的目标仅仅是能够简单的实现几个场景切换就可以了。为此,我上网 搜索了openGL的教程。因为我打算用QT开发,所以最后找到了qiliang前辈(技术上的)的一篇QT  openGL教程。如果大家对此更感兴趣,可以登陆他的网页学习这篇教程:http://www.qiliang.net/nehe_qt/index.html。我下面则以我打算开发的(简单)三维导航系统为主线,来分享我对openGL技术的理解。

1.糖果盒子
    首先是场景的建立。我打算将我所有的场景都放在一个正方体(或长方体)里,并把它想象成一个糖果盒子,把正对自己的面删去。这样,看起来就仿佛是一个舞台,并且有立体的感觉。对于怎样构造这样的一个舞台,我将主要步骤例举出来,并在随后补充相关的知识:
①画一个立方体,并不要画正面。这就好像你推开门走进一间房,我们现在删掉门,让房间里的布局映入我们眼帘;
②为我们的立方体的每个面(除开正面)设置贴图,让它看起来像真实的立体场景。这里,应该是5个面;
③将贴图设置成半透明的,这样看起来更加生动。
④在必要的时候旋转场景

   我们先对第一步进行详细说明。画立方体的基础是你要知道如何画一个面。怎样画一个面是有顺序要求的,而画一个立方体的各个面则没有,你可以先画其中任意的 一面。对于画一个面则需要遵循下面的步骤:先右上坐标,然后左上,然后左下,最后右下。即按逆时针的方向画。至于为什么要这样我不太清楚,但只需知道就 行。我们按上面的要求将立方体的五个面都画好。代码部分很简单,你可以看qiliang的QT openGL教程,这里不再贴出。
     第二步是为每个面设置贴图。贴图只是一个形象的称呼,专业的术语叫做纹理贴图(texture).纹理贴图的设置需要在画一个面之前。如果你想每个面的贴 图不同,那么你需要在绘制每个面之前绑定你的纹理贴图。这里,我给出一些简单的代码来说明。而使用的纹理贴图需要先设置好图片路径及参数。
代码,为绘制的面选择纹理贴图:
    glBindTexture( GL_TEXTURE_2D, texture[0] );
    glBegin(GL_QUADS);
    glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0,  1.0 );
    glTexCoord2f( 1.0, 0.0 ); glVertex3f(  1.0, -1.0,  1.0 );
    glTexCoord2f( 1.0, 1.0 ); glVertex3f(  1.0,  1.0,  1.0 );
    glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0,  1.0,  1.0 );
    glEnd();

    glBindTexture( GL_TEXTURE_2D, texture[1] );
    glBegin(GL_QUADS);
    glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );
    glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0,  1.0, -1.0 );
    glTexCoord2f( 0.0, 1.0 ); glVertex3f(  1.0,  1.0, -1.0 );
    glTexCoord2f( 0.0, 0.0 ); glVertex3f(  1.0, -1.0, -1.0 );
    glEnd();
这里的texture[0]和texture[1]就是我们使用的纹理贴图,而纹理贴图的设置部分如下:
  QImage img1,img2,;
  QImage buf;
  if ( !buf.load( "./1.bmp" ) )
  {
    qWarning( "Could not read image file, using single-color instead." );
    QImage dummy( 128, 128, QImage::Format_RGB32 );
    //dummy.fill( Qt::green.rgb() );

    buf = dummy;
  }
  img1 = QGLWidget::convertToGLFormat( buf );


  if ( !buf.load( "./2.bmp" ) )
  {
    qWarning( "Could not read image file, using single-color instead." );
    QImage dummy( 128, 128, QImage::Format_RGB32 );
    //dummy.fill( Qt::green.rgb() );

    buf = dummy;
  }
  img2 = QGLWidget::convertToGLFormat( buf );

//1
  glBindTexture( GL_TEXTURE_2D, texture[0] );
  glTexImage2D( GL_TEXTURE_2D, 0, 3, img1.width(), img1.height(), 0,
      GL_RGBA, GL_UNSIGNED_BYTE, img1.bits() );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
//2
  glBindTexture( GL_TEXTURE_2D, texture[1] );
  glTexImage2D( GL_TEXTURE_2D, 0, 3, img2.width(), img2.height(), 0,
      GL_RGBA, GL_UNSIGNED_BYTE, img2.bits() );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  
这一步完成后,我们就得到了一个拥有五个面的盒子。现在要做的就是使它看起来更加立体。这里,我们用到openGL融合的一些知识。其实很简单,如果你不深入下去,那么只用简单的添加几行代码到initializeGL()里面,它们是:
  glColor4f( 1.0, 1.0, 1.0, 0.5 );
  glBlendFunc( GL_SRC_ALPHA, GL_ONE );
并使效果enable:
  glEnable( GL_BLEND );
  glDisable( GL_DEPTH_TEST );

   好了,完成上面的步骤后,我们的场景已经建立好了。
   我们在设置场景的同时也会考虑能否旋转或者移动它以达到某些效果。所以最后,我们需要为之做一些铺垫,使我们的场景可以按我们的意愿旋转、移动。
   这里我们用到glRotatef( xRot,  1.0,  0.0,  0.0  )这个函数。第一个参数代表旋转地角度,后面三个参数是我们的对象相对于xyz轴旋转的位置.假设我们现在要旋转我们的场景,那么我们可以定义一个xRot变量,在最开始将其赋值为0.0,并在每次画图后xRot+=2之后updateGL()一下。这样我们将会得到一个绕x轴正向1.0位置旋转地 对象。你可以任意改变绕xyz轴旋转的位置,但不要超出你场景的大小。
   而移动场景则用glTranslatef(  0.0,  0.0, zoom )函数,三个参数分别是xyz轴,zoom为正值表示沿z轴正方向移动,负值表示沿负方向(即屏幕后方)移动。

2.单薄的小人
   上一节中我们展示了如何创建一个看似立体的场景,当然,我们只是靠一些纹理贴图让它看起来立体,而不是真正的去构建三维模型。在这一节中,我们要在前面的 场景中加入一些元素。我们把这些加入的元素想象成人、汽车或者是其他的一些东西。而我们的目标是让它们看起来是在场景中移动。
   还记得上一节中我们只绘制了5个面吗,现在,我们把第六个面加进来,但是并不将它作为我们立方体的正面。你可以把第六个面想象成皮影戏中的小人,而我们要 完成的工作仅仅是让这个单薄的小人在我们的场景中移动。当然,这看起来十分的简单,当我做完时我也感觉到完成这项工作其实对于学习openGL没有什么实 质性的进展,所以我决定在下一节中与大家分享构建多场景,并且加入立体元素的内容。而这一节,也希望你能耐心的看下去,你将从中学会怎样移动一个对象和openGL中的坐标系统。
   场景或对象的移动与旋转是在一开始遍完成的。这么说的原因在于,每当我们移动或旋转一个对象时,其实我们在进行updateGL(),即重绘我们的场景和 对象。想象一下,我们是如何画一个面的,当时我们用的是确定的一些坐标。这么说我想你应该能反应过来,对象和场景的移动其实就是用到为变量的坐标,我们对 这些变量进行操作,然后update一下。
   对于坐标系统D. Michael Traub:提供了对 Xvector , Yvector 和 Zvector 的上述解释。

为了更好的理解X, Y 和 Z的旋转,我们看一些例子...

X轴-您正在使用一台台锯。锯片中心的轴从左至右摆放(就像OpenGL中的X轴)。尖利的锯齿绕着X轴狂转,看起来要么向上转,要么向下转。取决 于锯片开始转时的方向。这与我们在OpenGL中绕着X轴旋转什么的情形是一样的。(CKer注:这会儿您要把脸蛋凑向显示器的话,保准被锯开了花 ^-^。)

Y轴-假设您正处于一个巨大的龙卷风中心,龙卷风的中心从地面指向天空(就像OpenGL中的Y轴)。垃圾和碎片围着Y轴从左向右或是从右向左狂转不止。这与我们在OpenGL中绕着Y轴旋转什么的情形是一样的。

Z轴-您从正前方看着一台风扇。风扇的中心正好朝着您(就像OpenGL中的Z轴)。风扇的叶片绕着Z轴顺时针或逆时针狂转。这与我们在OpenGL中绕着Z轴旋转什么的情形是一样的。
  
  当你完全明白以上所说的知识时,你会自己编写代码让我们单薄的小人在场景中移动了。不过,是不是觉得一点都不兴奋,你会说,为什么我们不构造一个立体的小人呢...
  下面我为下一节做些铺垫,我们将会探讨:

①构建一个三维场景,且里面的物体也是立体的,我们的小人也是立体的。如果我们构造的物体看起来都差不多,我们会考虑使用displayList显示列表
②构建其他的三维场景,并且我们可以在需要的时候切换这些场景。这里,我们学会构建一些类,用它来储存我们的场景信息
③从文件读入我们的三维场景,当然,文件中保存了我们所需要的所有信息
④一些基本图形的绘制简介,包括圆柱、贝塞尔曲面(这些都仅作介绍)

3.看起来不那么像的汽车
  让我们回顾下上一节,我们构建了一个单薄的小人,可它看起来并不让我们满意。这一节中,我们将介绍:
①构建一个三维场景,且里面的物体也是立体的,我们的小人也是立体的。如果我们构造的物体看起来都差不多,我们会考虑使用displayList显示列表
②构建其他的三维场景,并且我们可以在需要的时候切换这些场景。这里,我们学会构建一些类,用它来储存我们的场景信息
③从文件读入我们的三维场景,当然,文件中保存了我们所需要的所有信息
④一些基本图形的绘制简介,包括圆柱、贝塞尔曲面(这些都仅作介绍)


  首先让我们闭上眼睛想象一下,我们将要构建的是一个真实的立体场景,而不是前两节中所说的看似立体的场景。这里,我们将构造很多立体的图形,比如,正方体、长方体、圆柱体或是一些复杂图形。当然,简单的画出这些对象并不是我们的目的。我们将:

①构造一个scene类,它就是我们前两节中构建的糖果盒子。这次,我们将把许多立体元素加入进来,这就需要我们再构造一些正方体(长方体)类、圆柱体类,和一些复杂图形类。完成之后我们将在scene类中用到它们。那,让我们思考一下,我们的scene类需要包含些什么东西。

//H文件
class scene:public QGLWidget
{
    Q_OBJECT
public:
   scene( QWidget* parent = 0);
   ~scene();
   void draw(int id);  //调用它,我们将开始paint我们的对象
   curbe* myCurbe;     /*创建一个curbe类型的指针,我们用它来画我们所有的立方体。当然curbe类需要 

                         我们去定义*/
   int R;              //对象旋转地角度
   GLuint texture[3];  //所需的纹理。因为我不会美工也懒得找图,所以只用了3个简单的纹理图


protected:
  void loadGLTextures();

private:
  int sceneID;   //我们所绘场景的ID号。想象我们需要很多不同的场景,并且通过场景ID进行切换
  int objNum;    //场景中对象的数量
  char sort;     //表明我们所画对象的类型,curbe,圆柱或者贝塞尔曲面等
  char c;        //用来从文件中读入字符的变量
  string mapLine; //文件中的每一行我们存贮在mapLine中
  int i;
  string temp;
  void drawCurbe(); //画立方体时调用
  void paintMainScene(); //创建主场景,就是我们“舞台”的背景
  void creatObj();  //创建对象,它会去读取文件
  void recordCurbeData();  //读取到得文件,我们用它来初始化我们的数据
  void initializeGL();
  void paintGL();

};


②然后我们在cpp文件中进行我们的文件读取以及绘图等操作
  首先,我们绘图的入口在这里
void scene::draw(int id)
{
    sceneID = id;
    paintGL();
}

紧接着我们的paintGL函数是这样的:
void scene::paintGL()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    paintMainScene();
    creatObj();   //then ,record data
    loadGLTextures();
    R+=5;

}其中,我们先画我们的场景背景(就是我们前面画的糖果盒子),然后用creatObj()建立其他三维对象。

creatObj完成我们对文件的读写。文件中按一定格式存储着我们所画对象的信息。我们在creatObj()和recordCurbeData()等函数中去完成文件解析。
ps:最开始我只实现了从文件中读取立方体的数据然后将它们画到我们的场景中。而且由于能力有限,我只能将数据存储在txt文件中。希望对这方面有研究的朋友能告诉我更好的文件存储方法。
存储的数据是这样的:
1   //场景ID
2   //场景中对象数量
0,120,120,120,-120,0,-120,-120,0,0,0,0,0,0,0,-120n
对象类型(0代表curbe),接着三个是长宽高,再接着的是对象底面左上、左下、右下、右上的三维坐标。最后以n结束
0,120,120,60,-120,0,0,-120,0,60,-60,0,60,-60,0,0n
上面,我构造了两个立方体,我们可以再构造其他类型的对象,这里不再详细介绍。

③上面的操作我没有用displayList显示列表,因为显示列表用于创建许多一样的对象,而上面的操作我画了很多大小不一的对象。再次回顾下我们的目标,一个三维导航系统(虽然说我只能做的很简陋),上面的步骤我们用来构建静态的东西,然后我们用显示列表画我们的汽车。当然,这里需要用到我们的贝塞尔曲面,因为我不知道怎样画汽车,不过我发现贝塞尔曲面和它很像。
关于显示列表,简单的说就是它把我们要画的对象预先画在内存中,以便在实际画的时候能够加快效率。下面我们创建一个贝塞尔曲面的显示列表,具体的做法请参考Nehe的中文版教程第28讲。我这里只列出相关的函数

④initBezier()      //用来初始化贝塞尔曲面的16个控制点(4X4)
  LoadCarTexture();  // 载入纹理
  mybezier.dlBPatch = genBezier(mybezier, divs);  // 创建显示列表
  glCallList(mybezier.dlBPatch);   // 调用显示列表,绘制贝塞尔曲面
  其中genBezier()生成贝塞尔曲面的显示列表,它是这个样子的:
  GLuint openGLCar::genBezier(BEZIER_PATCH patch, int divs) {
        int  u = 0, v;
        float  py, px, pyold;
        GLuint  drawlist = glGenLists(1);   // 创建显示列表
        POINT_3D temp[4];
        POINT_3D *last = (POINT_3D*)malloc(sizeof(POINT_3D)*(divs+1)); // 根据每一条曲 

                                              线的细分数,分配相应的内存

        if (patch.dlBPatch != NULL)   // 如果显示列表存在则删除
                glDeleteLists(patch.dlBPatch, 1);

        temp[0] = patch.anchors[0][3];    // 获得u方向的四个控制点
        temp[1] = patch.anchors[1][3];
        temp[2] = patch.anchors[2][3];
        temp[3] = patch.anchors[3][3];

        for (v=0;v<=divs;v++) {    // 根据细分数,创建各个分割点额参数
                px = ((float)v)/((float)divs);
        // 使用Bernstein函数求的分割点的坐标
                last[v] = Bernstein(px, temp);
        }

        glNewList(drawlist, GL_COMPILE);   // 创建一个新的显示列表
       // glBindTexture(GL_TEXTURE_2D, patch.texture);   // 邦定纹理

        for (u=1;u<=divs;u++) {
                py    = ((float)u)/((float)divs);  // 计算v方向上的细分点的参数
                pyold = ((float)u-1.0f)/((float)divs);  // 上一个v方向上的细分点的参数

            temp[0] = Bernstein(py, patch.anchors[0]);// 计算每个细分点v方向上贝塞尔曲面的控制点
            temp[1] = Bernstein(py, patch.anchors[1]);
            temp[2] = Bernstein(py, patch.anchors[2]);
            temp[3] = Bernstein(py, patch.anchors[3]);

              glBegin(GL_TRIANGLE_STRIP);    // 开始绘制三角形带

                for (v=0;v<=divs;v++) {
                        px = ((float)v)/((float)divs);  // 沿着u轴方向顺序绘制

                        glTexCoord2f(pyold, px);   // 设置纹理坐标
                        glVertex3d(last[v].x, last[v].y, last[v].z); // 绘制一个顶点

                        last[v] = Bernstein(px, temp);  // 创建下一个顶点
                        glTexCoord2f(py, px);   // 设置纹理
                        glVertex3d(last[v].x, last[v].y, last[v].z); // 绘制新的顶点
                }

                glEnd();      // 结束三角形带的绘制
        }

        glEglCallList(mybezier.dlBPatch); // 调用显示列表,绘制贝塞尔曲面ndList(); 

    // 显示列表绘制结束

        free(last);      // 释放分配的内存
        return drawlist;      // 返回创建的显示列表
}

4.从milkshape3D导入场景
首先,让我们回归一下之前我们都完成了哪些:
1.建立了一个三维场景
2.用自定义文件及数据格式的方式构建了我们需要的简单对象(立方体,贝塞尔曲面)
3.在以上的建模及编程中,我们用到了诸如显示列表的一些东西

   这一节中,我们将研究怎样将milkshape3D的模型场景加载到我们的程序中,这样,我们就可以在程序外部编辑我们的场景,然后把它们加载进来。不过,如果是一些需要时刻改变的对象,比如我们的汽车,最好还是单独加载并加以控制的代码。以下的代码都是基于QT+openGL实现的,有任何疑问可以给我留言,我会尽快回复。
   对于milkshape3D模型场景的导入,我们首先需要明白几个概念。
1.milkshape3D是什么。简单的说,它就是和大家熟知的3DMAX相似的一种3D建模软件,存储的文件格式为xx.ms3d(xx代表文件名)。我们在实际的文件导入中,需要将它以2进制的形式打开,并将里面的信息存放进我们事先定义好的结构体中

2.需要哪些结构体。我们所用到的结构体都是用来存储ms3d文件信息的,它们包括下面列出的元素
  MS3DHeader     /*包含ms3d文件的版本信息*、
  MS3DVertex     /*顶点信息*/
  MS3DMaterial   /*材质(纹理贴图等)信息*/
  MS3DTriangle   /*绘制三角形信息*/
  MS3DJoint      /*节点(骨骼)信息*/
  MS3DKeyframe   /*关键窗口*/
其中,MS3DJoint和MS3DKeyframe我们这一节中用不到,不过我们还是给出他们的定义。
下面的结构体是存放上面结构体解析后的信息
  Mesh   /*网格*/
  Material  /*材质*/
  Triangle  /*三角形*/
  Vertex   /*定点*/
关于3D建模的知识,很抱歉,我只是一个门外汉,所以无法跟大家解释我们的三维图形是如何一步一步画出来的,不过我知道他们的步骤,并按部就班的把信息解析出来,然后再画到我们的openGL中。

3.特别强调。关于以上结构体的定义和我们的实际编程,我们在解析2进制文件的时候需要用到字节对齐。不清楚的朋友请自行补上,我们这个用的是1字节对齐,即有如下形式:
# pragma pack( 1 )
# define PACK_STRUCT
PACK_STRUCT为我们结构体的别名

对于QT+linux的环境,我们需要作两个变量定义
typedef unsigned char  byte;
typedef unsigned short word;

明白了这些概念后,就是按部就班的解析并存储信息了
1.loadModelData
bool scene::loadModelData( const char *filename )
{
        ifstream inputFile( filename, ios::in | ios::binary);
        if(inputFile.fail())
            return false;

//以二进制的方式打开文件,如果失败则返回

        inputFile.seekg( 0, ios::end );
        long fileSize = inputFile.tellg();
        inputFile.seekg( 0, ios::beg );

        byte *pBuffer = new byte[fileSize];
        inputFile.read( (char*)pBuffer, fileSize );
        inputFile.close();

//分配一个内存,载入文件,并关闭文件

        const byte *pPtr = pBuffer;
        MS3DHeader *pHeader = ( MS3DHeader* )pPtr;
        pPtr += sizeof( MS3DHeader );

        if ( strncmp( pHeader->m_ID, "MS3D000000", 10 ) != 0 )
        {       printf("不是一个有效的MS3D文件\n");
                return false; // 如果不是一个有效的MS3D文件则返回
            }

        if ( (pHeader->m_version < 3 )|| (pHeader->m_version > 4 ))
        {
            printf("不能支持这种版本的文件,%d\n",pHeader->m_version);
                return false; // 如果不能支持这种版本的文件,则返回失败
            }
        printf("支持这种版本的文件,Version=%d\n",pHeader->m_version);
//上面的文件读取文件头

        int nVertices = *( word* )pPtr;
        m_numVertices = nVertices;
        m_pVertices = new Vertex[nVertices];
        pPtr += sizeof( word );

        int i;
        for ( i = 0; i < nVertices; i++ )
        {

                MS3DVertex *pVertex = ( MS3DVertex* )pPtr;
                m_pVertices[i].m_boneID = pVertex->m_boneID;
                memcpy( m_pVertices[i].m_location, pVertex->m_vertex, sizeof( float )*3 );
                pPtr += sizeof( MS3DVertex );
        }


//上面的代码读取顶点数据


        int nTriangles = *( word* )pPtr;
        m_numTriangles = nTriangles;
        m_pTriangles = new Triangle[nTriangles];
        pPtr += sizeof( word );

        for ( i = 0; i < nTriangles; i++ )
        {
                MS3DTriangle *pTriangle = ( MS3DTriangle* )pPtr;
                int vertexIndices[3] = { pTriangle->m_vertexIndices[0], pTriangle-

>m_vertexIndices[1], pTriangle->m_vertexIndices[2] };
                float t[3] = { 1.0f-pTriangle->m_t[0], 1.0f-pTriangle->m_t[1], 1.0f-pTriangle-

>m_t[2] };
                memcpy( m_pTriangles[i].m_vertexNormals, pTriangle->m_vertexNormals, sizeof(

float )*3*3 );
                memcpy( m_pTriangles[i].m_s, pTriangle->m_s, sizeof( float )*3 );
                memcpy( m_pTriangles[i].m_t, t, sizeof( float )*3 );
                memcpy( m_pTriangles[i].m_vertexIndices, vertexIndices, sizeof( int )*3 );
                pPtr += sizeof( MS3DTriangle );
        }

//上面的代码用来读取三角形信息,因为MS3D使用窗口坐标系而OpenGL使用笛卡儿坐标系,所以需要反转每个顶点Y方向的纹理坐标


        int nGroups = *( word* )pPtr;
        m_numMeshes = nGroups;
        m_pMeshes = new Mesh[nGroups];
        pPtr += sizeof( word );
        for ( i = 0; i < nGroups; i++ )
        {
                pPtr += sizeof( byte );
                pPtr += 32;

                word nTriangles = *( word* )pPtr;
                pPtr += sizeof( word );
                int *pTriangleIndices = new int[nTriangles];
                for ( int j = 0; j < nTriangles; j++ )
                {
                        pTriangleIndices[j] = *( word* )pPtr;
                        pPtr += sizeof( word );
                }

                char materialIndex = *( char* )pPtr;
                pPtr += sizeof( char );

                m_pMeshes[i].m_materialIndex = materialIndex;
                m_pMeshes[i].m_numTriangles = nTriangles;
                m_pMeshes[i].m_pTriangleIndices = pTriangleIndices;
        }


//上面的代码填充网格结构


        int nMaterials = *( word* )pPtr;
        m_numMaterials = nMaterials;
        m_pMaterials = new Material[nMaterials];
        pPtr += sizeof( word );
        for ( i = 0; i < nMaterials; i++ )
        {
                MS3DMaterial *pMaterial = ( MS3DMaterial* )pPtr;
                memcpy( m_pMaterials[i].m_ambient, pMaterial->m_ambient, sizeof( float )*4 );
                memcpy( m_pMaterials[i].m_diffuse, pMaterial->m_diffuse, sizeof( float )*4 );
                memcpy( m_pMaterials[i].m_specular, pMaterial->m_specular, sizeof( float )*4 );
                memcpy( m_pMaterials[i].m_emissive, pMaterial->m_emissive, sizeof( float )*4 );
                m_pMaterials[i].m_shininess = pMaterial->m_shininess;
                m_pMaterials[i].m_pTextureFilename = new char[strlen( pMaterial->m_texture )+1];
                strcpy( m_pMaterials[i].m_pTextureFilename, pMaterial->m_texture );
                pPtr += sizeof( MS3DMaterial );
        }

        reloadTextures();


// 上面的代码加载纹理数据


        delete[] pBuffer;

        return true;
}

2.drawModel
void scene::drawModel()
{

   glLoadIdentity();

   glTranslatef( 0.0,  0.0, -4.0 );

//gluLookAt( 75, 75, 75, 0, 0, 0, 0, 1, 0 );  // (3) Eye Postion (3) Center Point (3) Y-Axis    

                                                                                  Up Vector
        // 按网格分组绘制
        for ( int i = 0; i < m_numMeshes; i++ )
        {

                int materialIndex = m_pMeshes[i].m_materialIndex;
                if ( materialIndex >= 0 )
                {
                        printf("materialIndex > 0\n");
                        glMaterialfv( GL_FRONT, GL_AMBIENT, m_pMaterials

[materialIndex].m_ambient );
                        glMaterialfv( GL_FRONT, GL_DIFFUSE, m_pMaterials

[materialIndex].m_diffuse );
                        glMaterialfv( GL_FRONT, GL_SPECULAR, m_pMaterials

[materialIndex].m_specular );
                        glMaterialfv( GL_FRONT, GL_EMISSION, m_pMaterials

[materialIndex].m_emissive );
                        glMaterialf( GL_FRONT, GL_SHININESS, m_pMaterials

[materialIndex].m_shininess );

                        if ( m_pMaterials[materialIndex].m_texture > 0 )
                        {
                                printf("m_pMaterials[materialIndex].m_texture > 0\n");
                                glBindTexture( GL_TEXTURE_2D, m_pMaterials

[materialIndex].m_texture );
                                glEnable( GL_TEXTURE_2D );
                        }
                        else
                                glEnable( GL_TEXTURE_2D );//disable instead
                }
                else
                {
                        glEnable( GL_TEXTURE_2D );//disable instead
                }

                glBegin( GL_TRIANGLES );
                {
                       for ( int j = 0; j < m_pMeshes[i].m_numTriangles; j++ )
                        {
                                int triangleIndex = m_pMeshes[i].m_pTriangleIndices[j];
                                const Triangle* pTri = &m_pTriangles[triangleIndex];

                                for ( int k = 0; k < 3; k++ )
                                {
                                        int index = pTri->m_vertexIndices[k];

                                        glNormal3fv( pTri->m_vertexNormals[k] );
                                        glTexCoord2f( pTri->m_s[k], pTri->m_t[k] );
                                        glVertex3fv( m_pVertices[index].m_location );
                                }
                        }
                }
                glEnd();
        }

                glEnable( GL_TEXTURE_2D );

      
}

上面的代码我并没有将纹理贴图部分加上,添加的方法如果你阅读过前几节应该不难做到。整个过程中最容易出问题的地方可能在模型加载成功后的不可见上。这里特别强调,milkshape3D中的坐标系统与QT+openGL里的并不那么一致,所以可能你加载的模型场景太大所以显示不出来,或者整个屏幕都显示一种颜色。这里给出一种简单的解决办法(P.S.很抱歉初学的我还没找到正规的解决办法):大家翻回到前几节可能会注意到,我们绘制的图形的大小,即我们可是屏幕的大小在-2到+2之间,所以我们在加载milkshape3D模型前需要对其进行缩放以适应我们的QT

0 0