阅读 4

OpenGL NeHe on Linux 3

显示列表

#define TRUE 1
#define FALSE 0

/**
 * 这次我将教你如何使用显示列表,显示列表将加快程序的速度,而且可以减少代码的长度。当你在制作游戏里的小行星场景时,每一层上至少需要两个行星,你可以用OpenGL中的多边形来构造每一个行星。
 * 聪明点的做法是做一个循环,每个循环画出行星的一个面,最终你用几十条语句画出了一个行星。每次把行星画到屏幕上都是很困难的。当你面临更复杂的物体时你就会明白了。
 * 那么,解决的办法是什么呢?用现实列表,你只需要一次性建立物体,你可以贴图,用颜色,想怎么弄就怎么弄。给现实列表一个名字,比如给小行星的显示列表命名为“asteroid”。
 * 现在,任何时候我想在屏幕上画出行星,我只需要调用glCallList(asteroid)。之前做好的小行星就会立刻显示在屏幕上了。因为小行星已经在显示列表里建造好了,OpenGL不会再计算如何构造它。
 * 它已经在内存中建造好了。这将大大降低CPU的使用,让你的程序跑的更快。
 */
/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson12::LoadBmp(const char* filename,
                  AUX_RGBImageRec* texture_image) {
    FREE_IMAGE_FORMAT fifmt = FreeImage_GetFileType(filename, 0);
    FIBITMAP *dib = FreeImage_Load(fifmt, filename, 0);
    dib = FreeImage_ConvertTo24Bits(dib);
    int width = FreeImage_GetWidth(dib);
    int height = FreeImage_GetHeight(dib);
    BYTE *pixels = (BYTE*) FreeImage_GetBits(dib);
    int pix = 0;
    if (texture_image == NULL)
        return FALSE;
    texture_image->data = (BYTE *) malloc(width * height * 3);
    texture_image->sizeX = width;
    texture_image->sizeY = height;
    for (pix = 0; pix < width * height; pix++) {
        texture_image->data[pix * 3 + 0] = pixels[pix * 3 + 2];
        texture_image->data[pix * 3 + 1] = pixels[pix * 3 + 1];
        texture_image->data[pix * 3 + 2] = pixels[pix * 3 + 0];
    }
    FreeImage_Unload(dib);
    return TRUE;
}

/**
 * 下一部分代码载入位图(调用上面的代码)并转换成纹理。
 * @return
 */
int Lesson12::LoadGLTextures() {
    /**
     * 然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。 Status 缺省设为 FALSE (表示没有载入或创建任何东东)。
     */
    int Status = FALSE;                            // 状态指示器
    /**
     * 现在我们创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。
     */
    AUX_RGBImageRec *TextureImage;                    // 创建纹理的存储空间
    TextureImage = (AUX_RGBImageRec *)malloc(sizeof(AUX_RGBImageRec));
    /**
     * 现在载入位图,并将其转换为纹理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 调用 LoadBMP() 的代码。
     * 载入 Data 目录下的 NeHe.bmp 位图文件。如果一切正常,图像数据将存放在 TextureImage[0] 中, Status 被设为 TRUE ,然后我们开始创建纹理。
     */
    // 载入位图,检查有无错误,如果位图没找到则退出
    if (LoadBmp("/home/zpw/Documents/CLionProjects/opengl_study/Data/Cube.bmp", TextureImage)) {
        Status = TRUE;                            // 将 Status 设为 TRUE
        /**
         * 现在使用中 TextureImage[0] 的数据创建纹理。第一行 glGenTextures(1, &texture[0]) 告诉OpenGL我们想生成一个纹理名字(如果您想载入多个纹理,加大数字)。
         * 值得注意的是,开始我们使用 GLuint texture[1] 来创建一个纹理的存储空间,您也许会认为第一个纹理就是存放在 &texture[1] 中的,但这是错的。
         * 正确的地址应该是 &texture[0] 。同样如果使用 GLuint texture[2] 的话,第二个纹理存放在 texture[1] 中。
         */
        /**
         * 第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告诉OpenGL将纹理名字 texture[0] 绑定到纹理目标上。
         * 2D纹理只有高度(在 Y 轴上)和宽度(在 X 轴上)。主函数将纹理名字指派给纹理数据。本例中我们告知OpenGL, &texture[0] 处的内存已经可用。我们创建的纹理将存储在 &texture[0] 的 指向的内存区域。
         */
        glGenTextures(1, &texture[0]);                    // 创建纹理

        // 使用来自位图数据生成 的典型纹理
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        /**
         * 下来我们创建真正的纹理。下面一行告诉OpenGL此纹理是一个2D纹理 ( GL_TEXTURE_2D )。
         * 参数“0”代表图像的详细程度,通常就由它为零去了。参数三是数据的成分数。因为图像是由红色数据,绿色数据,蓝色数据三种组分组成。
         * TextureImage[0]->sizeX 是纹理的宽度。如果您知道宽度,您可以在这里填入,但计算机可以很容易的为您指出此值。
         * TextureImage[0]->sizey 是纹理的高度。参数零是边框的值,一般就是“0”。 GL_RGB 告诉OpenGL图像数据由红、绿、蓝三色数据组成。
         * GL_UNSIGNED_BYTE 意味着组成图像的数据是无符号字节类型的。最后... TextureImage[0]->data 告诉OpenGL纹理数据的来源。此例中指向存放在 TextureImage[0] 记录中的数据。
         */
        // 生成纹理
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     TextureImage->data);
        /**
         * 下面的两行告诉OpenGL在显示图像时,当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时OpenGL采用的滤波方式。
         * 通常这两种情况下我都采用 GL_LINEAR 。这使得纹理从很远处到离屏幕很近时都平滑显示。使用 GL_LINEAR 需要CPU和显卡做更多的运算。
         * 如果您的机器很慢,您也许应该采用 GL_NEAREST 。过滤的纹理在放大的时候,看起来斑驳的很『译者注:马赛克啦』。您也可以结合这两种滤波方式。在近处时使用 GL_LINEAR ,远处时 GL_NEAREST 。
         */
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    // 线形滤波
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    // 线形滤波
    }
    /**
     * 现在我们释放前面用来存放位图数据的内存。我们先查看位图数据是否存放在处。如果是的话,再查看数据是否已经存储。如果已经存储的话,删了它。接着再释放 TextureImage[0] 图像结构以保证所有的内存都能释放。
     */
    if (TextureImage)                            // 纹理是否存在
    {
        if (TextureImage->data)                    // 纹理图像是否存在
        {
            free(TextureImage->data);                // 释放纹理图像占用的内存
        }

        free(TextureImage);                        // 释放图像结构
    }
    /**
     * 最后返回状态变量。如果一切OK,变量 Status 的值为 TRUE 。否则为 FALSE 。
     */
    return Status;
}

/**
 * 现在正式开始建立显示列表。你可能注意到了,所有创造盒子的代码都在第一个显示列表里,所有创造顶部的代码都在另一个列表里。
 */
GLvoid Lesson12::BuildLists()					// 创建盒子的显示列表
{
    /**
     * 开始的时候我们告诉OpenGL我们要建立两个显示列表。glGenLists(2)建立了两个显示列表的空间,并返回第一个显示列表的指针。“box”指向第一个显示列表,任何时候调用“box”第一个显示列表就会显示出来。
     */
    box=glGenLists(2);				// 创建两个显示列表的名称
    /**
     * 现在开始构造第一个显示列表。我们已经申请了两个显示列表的空间了,并且有box指针指向第一个显示列表。所以现在我们应该告诉OpenGL要建立什么类型的显示列表。
     * 我们用glNewList()命令来做这个事情。你一定注意到了box是第一个参数,这表示OpenGL将把列表存储到box所指向的内存空间。
     * 第二个参数GL_COMPILE告诉OpenGL我们想预先在内存中构造这个列表,这样每次画的时候就不必重新计算怎么构造物体了。GL_COMPILE类似于编程。
     * 在你写程序的时候,把它装载到编译器里,你每次运行程序都需要重新编译。而如果他已经编译成了.exe文件,那么每次你只需要点击那个.exe文件就可以运行它了,不需要编译。
     * 当OpenGL编译过显示列表后,就不需要再每次显示的时候重新编译它了。这就是为什么用显示列表可以加快速度。
     */
    glNewList(box,GL_COMPILE);			// 创建第一个显示列表
    /**
     * 下面这部分的代码画出一个没有顶部的盒子,它不会出现在屏幕上,只会存储在显示列表里。你可以在glNewList()和glEngList()中间加上任何你想加上的代码。
     * 可以设置颜色,贴图等等。唯一不能加进去的代码就是会改变显示列表的代码。显示列表一旦建立,你就不能改变它。
     * 比如你想加上glColor3ub(rand()%255,rand()%255,rand()%255),使得每一次画物体时都会有不同的颜色。但因为显示列表只会建立一次,所以每次画物体的时候颜色都不会改变。
     * 物体将会保持第一次建立显示列表时的颜色。 如果你想改变显示列表的颜色,你只有在调用显示列表之前改变颜色。后面将详细解释这一点。
     */
    glBegin(GL_QUADS);							// 开始绘制四边形
    // 底面
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    // 前面
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    // 后面
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    // 右面
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    // 左面
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glEnd();								// 四边形绘制结束
    /**
     * 用glEngList()命令,我们告诉OpenGL我们已经完成了一个显示列表。在glNewList()和glEngList()之间的任何东西就是显示列表的一部分。
     */
    glEndList();									// 第一个显示列表结束

    /**
     * 现在我们来建立第二个显示列表。在上一个显示列表的指针上加1,就得到了第二个显示列表的指针。第二个显示列表的指针命名为“top”。
     */
    top=box+1;									// 第二个显示列表的名称
    glNewList(top,GL_COMPILE);							// 盒子顶部的显示列表
    /**
     * 下面的代码画出盒子的顶部。
     */
    glBegin(GL_QUADS);							// 开始绘制四边形
    // 上面
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glEnd();								// 结束绘制四边形
    glEndList();									// 第二个显示列表创建完毕
}

/**
 * 下面的代码的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您没有使用全屏模式)。
 * 甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运行一次--在程序开始时设置我们的透视图。
 * OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。
 * @param width
 * @param height
 * @return
 */
GLvoid Lesson12::ReSizeGLScene(GLsizei width, GLsizei height)                // 重置OpenGL窗口大小
{
    if (height == 0)                                // 防止被零除
    {
        height = 1;                            // 将Height设为1
    }

    glViewport(0, 0, width, height);                    // 重置当前的视口
    /**
     * 下面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。
     * 此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。
     *
     * glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projection matrix(投影矩阵)。投影矩阵负责为我们的场景增加透视。
     * glLoadIdentity()近似于重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。
     *
     * glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。
     */
    glMatrixMode(GL_PROJECTION);                        // 选择投影矩阵
    glLoadIdentity();                            // 重置投影矩阵

    // 设置视口的大小
    gluPerspective(45.0f, (GLfloat) width / (GLfloat) height, 0.1f, 100.0f);

    glMatrixMode(GL_MODELVIEW);                        // 选择模型观察矩阵
    glLoadIdentity();                            // 重置模型观察矩阵
}

/**
 * 接下的代码段中,我们将对OpenGL进行所有的设置。我们将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑),等等。这个例程直到OpenGL窗口创建之后才会被调用。
 * if (!LoadGLTextures()) 这行代码调用上面讲的子例程载入位图并生成纹理。如果因为任何原因 LoadGLTextures() 调用失败,接着的一行返回FALSE。如果一切OK,并且纹理创建好了,我们启用2D纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 * @return
 */
int Lesson12::InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())							// 调用纹理载入子例程
    {
        return FALSE;							// 如果未能载入,返回FALSE
    }
    glEnable(GL_TEXTURE_2D);						// 启用纹理映射

    BuildLists();						// 创建显示列表
    /**
     * 接下来的三行使灯光有效。Light0一般来说是在显卡中预先定义过的,如果Light0不工作,把下面那行注释掉好了。
     * 最后一行的GL_COLOR_MATERIAL使我们可以用颜色来贴纹理。如果没有这行代码,纹理将始终保持原来的颜色,glColor3f(r,g,b)就没有用了。总之这行代码是很有用的。
     */
    glEnable(GL_LIGHT0);					// 使用默认的0号灯
    glEnable(GL_LIGHTING);					// 使用灯光
    glEnable(GL_COLOR_MATERIAL);				// 使用颜色材质

    /**
     * 下一行启用smooth shading(阴影平滑)。阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑。
     */
    glShadeModel(GL_SMOOTH);                        // 启用阴影平滑
    /**
     * 下一行设置清除屏幕时所用的颜色。如果您对色彩的工作原理不清楚的话,我快速解释一下。
     * 色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。
     * glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。
     *
     * 通过混合三种原色(红、绿、蓝),您可以得到不同的色彩。因此,当您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您将用亮蓝色来清除屏幕。
     * 如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,您将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。
     * 要得到白色背景,您应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,您该将所有的颜色设为最暗(0.0f)。
     */
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);					// 黑色背景
    /**
     * 接下来的三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
     * 我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。
     * 这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。
     */
    glClearDepth(1.0f);                            // 设置深度缓存
    glEnable(GL_DEPTH_TEST);                        // 启用深度测试
    glDepthFunc(GL_LEQUAL);                            // 所作深度测试的类型
    /**
     * 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
     */
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // 告诉系统对透视进行修正
    return TRUE;                                // 初始化 OK
}

/**
 * 下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。
 * 如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。
 * @return
 */
int Lesson12::DrawGLScene(GLvoid)                                // 从这里开始进行所有的绘制
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // 清除屏幕和深度缓存

    glBindTexture(GL_TEXTURE_2D, texture[0]);				// 选择纹理

    /**
     * 现在到了真正有趣的地方了。用一个循环,循环变量用于改变Y轴位置,在Y轴上画5个立方体,所以用从1到5的循环。
     */
    for (yloop=1;yloop<6;yloop++)				// 沿Y轴循环
    {
        /**
         * 另外用一个循环,循环变量用于改变X轴位置。每行上的立方体数目取决于行数,所以循环方式如下。
         */
        for (xloop=0;xloop<yloop;xloop++)		// 沿X轴循环
        {
            glLoadIdentity();			// 重置模型变化矩阵
            // 设置盒子的位置
            glTranslatef(1.4f+(float(xloop)*2.8f)-(float(yloop)*1.4f),((6.0f-float(yloop))*2.4f)-7.0f,-20.0f);
            glRotatef(45.0f-(2.0f*yloop)+xrot,1.0f,0.0f,0.0f);
            glRotatef(45.0f+yrot,0.0f,1.0f,0.0f);
            glColor3fv(boxcol[yloop-1]);
            glCallList(box);			// 绘制盒子
            glColor3fv(topcol[yloop-1]);		// 选择顶部颜色
            glCallList(top);			// 绘制顶部
        }
    }
    return TRUE;                                //  一切 OK
}
复制代码

3D 模型加载和渲染

#include <GL/gl.h>

class Model {
public:
    // 材质属性
    struct Mesh {
        int m_materialIndex;
        int m_numTriangles;
        int *m_pTriangleIndices;
    };

    // 网格结构
    struct Material {
        float m_ambient[4], m_diffuse[4], m_specular[4], m_emissive[4];
        float m_shininess;
        GLuint m_texture;
        char *m_pTextureFilename;
    };

    // 三角形结构
    struct Triangle {
        float m_vertexNormals[3][3];
        float m_s[3], m_t[3];
        int m_vertexIndices[3];
    };

    // 顶点结构
    struct Vertex {
        char m_boneID;	// 顶点所在的骨骼
        float m_location[3];
    };

public:
    Model();

    ~Model();

    virtual bool loadModelData(const char *filename) = 0;

    void draw();

    void reloadTextures();

public:
    // 使用的纹理
    int m_numMeshes;
    Mesh *m_pMeshes;

    // 使用的网格
    int m_numMaterials;
    Material *m_pMaterials;

    // 使用的三角形
    int m_numTriangles;
    Triangle *m_pTriangles;

    // 顶点的个数和数据
    int m_numVertices;
    Vertex *m_pVertices;
};

#include "MilkshapeModel.h"
#include <fstream>
#include <cstring>

using namespace std;

MilkshapeModel::MilkshapeModel() {}

MilkshapeModel::~MilkshapeModel() {}

typedef unsigned char byte;
typedef unsigned short word;

// byte-align structures
#ifdef _MSC_VER
#	pragma pack( push, packing )
#	pragma pack( 1 )
#	define PACK_STRUCT
#elif defined( __GNUC__ )
#	define PACK_STRUCT	__attribute__((packed))
#else
#	error you must byte-align these structures with the appropriate compiler directives
#endif

struct MS3DHeader {
    char m_ID[10];
    int m_version;
} PACK_STRUCT;

struct MS3DVertex {
    byte m_flags;
    float m_vertex[3];
    char m_boneID;
    byte m_refCount;
} PACK_STRUCT;

struct MS3DTriangle
{
    word m_flags;
    word m_vertexIndices[3];
    float m_vertexNormals[3][3];
    float m_s[3], m_t[3];
    byte m_smoothingGroup;
    byte m_groupIndex;
} PACK_STRUCT;

struct MS3DMaterial
{
    char m_name[32];
    float m_ambient[4];
    float m_diffuse[4];
    float m_specular[4];
    float m_emissive[4];
    float m_shininess;	// 0.0f - 128.0f
    float m_transparency;	// 0.0f - 1.0f
    byte m_mode;	// 0, 1, 2 is unused now
    char m_texture[128];
    char m_alphamap[128];
} PACK_STRUCT;

struct MS3DJoint
{
    byte m_flags;
    char m_name[32];
    char m_parentName[32];
    float m_rotation[3];
    float m_translation[3];
    word m_numRotationKeyframes;
    word m_numTranslationKeyframes;
} PACK_STRUCT;

struct MS3DKeyframe
{
    float m_time;
    float m_parameter[3];
} PACK_STRUCT;

#ifdef _MSC_VER
#	pragma pack( pop, packing )
#endif

#undef PACK_STRUCT

bool MilkshapeModel::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(reinterpret_cast<char *>(pBuffer), fileSize );
    inputFile.close();

    //读取文件头
    const byte *pPtr = pBuffer;
    MS3DHeader *pHeader = ( MS3DHeader* )pPtr;
    pPtr += sizeof( MS3DHeader );

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

    if ( pHeader->m_version < 3 || pHeader->m_version > 4 )
        return false; // 如果不能支持这种版本的文件,则返回失败

    //读取顶点数据
    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 );
    }

    //读取三角形信息,因为MS3D使用窗口坐标系而OpenGL使用笛卡儿坐标系,所以需要反转每个顶点Y方向的纹理坐标
    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 );
    }

    //填充网格结构
    int nGroups = *( word* )pPtr;
    m_numMeshes = nGroups;
    m_pMeshes = new Mesh[nGroups];
    pPtr += sizeof( word );
    for ( i = 0; i < nGroups; i++ )
    {
        pPtr += sizeof( byte );	// flags
        pPtr += 32;				// name

        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;
}

void Model::reloadTextures() {
    for ( int i = 0; i < m_numMaterials; i++ )
        if ( strlen( m_pMaterials[i].m_pTextureFilename ) > 0 )
            m_pMaterials[i].m_texture = LoadGLTextures("/home/zpw/Documents/CLionProjects/opengl_study/Data/Wood.bmp");
        else
            m_pMaterials[i].m_texture = 0;
}

void Model::draw() {
    //根据模型的信息,按网格分组,分别绘制每一组的数据。
    GLboolean texEnabled = glIsEnabled(GL_TEXTURE_2D);
    // 按网格分组绘制
    for (int i = 0; i < m_numMeshes; ++i) {
        int materialIndex = m_pMeshes[i].m_materialIndex;
        if (materialIndex >= 0) {
            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) {
                glBindTexture(GL_TEXTURE_2D, m_pMaterials[materialIndex].m_texture);
                glEnable(GL_TEXTURE_2D);
            } else {
                glDisable(GL_TEXTURE_2D);
            }
        } else {
            glDisable( GL_TEXTURE_2D );
        }

        glBegin(GL_TRIANGLES);
        {
            for (int j = 0; j < m_pMeshes[i].m_numTriangles; ++j) {
                int triangleIndex = m_pMeshes[i].m_pTriangleIndices[j];
                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();
    }

    if ( texEnabled )
        glEnable( GL_TEXTURE_2D );
    else
        glDisable( GL_TEXTURE_2D );
}
复制代码

#define TRUE 1
#define FALSE 0

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson16::LoadBmp(const char *filename,
                            AUX_RGBImageRec *texture_image) {
    FREE_IMAGE_FORMAT fifmt = FreeImage_GetFileType(filename, 0);
    FIBITMAP *dib = FreeImage_Load(fifmt, filename, 0);
    dib = FreeImage_ConvertTo24Bits(dib);
    int width = FreeImage_GetWidth(dib);
    int height = FreeImage_GetHeight(dib);
    BYTE *pixels = (BYTE *) FreeImage_GetBits(dib);
    int pix = 0;
    if (texture_image == NULL)
        return FALSE;
    texture_image->data = (BYTE *) malloc(width * height * 3);
    texture_image->sizeX = width;
    texture_image->sizeY = height;
    for (pix = 0; pix < width * height; pix++) {
        texture_image->data[pix * 3 + 0] = pixels[pix * 3 + 2];
        texture_image->data[pix * 3 + 1] = pixels[pix * 3 + 1];
        texture_image->data[pix * 3 + 2] = pixels[pix * 3 + 0];
    }
    FreeImage_Unload(dib);
    return TRUE;
}

/**
 * 下一部分代码载入位图(调用上面的代码)并转换成纹理。现在载入一个位图,并用它创建三种不同的纹理。这段代码调用前面的代码载入位图,并将其转换成3个纹理。Status 变量跟踪纹理是否已载入并被创建了。
 * @return
 */
int Lesson16::LoadGLTextures() {
    /**
     * 然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。 Status 缺省设为 FALSE (表示没有载入或创建任何东东)。
     */
    int Status = FALSE;                            // 状态指示器
    /**
     * 现在我们创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。
     */
    AUX_RGBImageRec *TextureImage;                    // 创建纹理的存储空间
    TextureImage = (AUX_RGBImageRec *) malloc(sizeof(AUX_RGBImageRec));
    /**
     * 现在载入位图,并将其转换为纹理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 调用 LoadBMP() 的代码。
     * 载入 Data 目录下的 NeHe.bmp 位图文件。如果一切正常,图像数据将存放在 TextureImage[0] 中, Status 被设为 TRUE ,然后我们开始创建纹理。
     */
    // 载入位图,检查有无错误,如果位图没找到则退出
    if (LoadBmp("/home/zpw/Documents/CLionProjects/opengl_study/Data/Crate.bmp", TextureImage)) {
        Status = TRUE;                            // 将 Status 设为 TRUE
        /**
         * 现在我们已经将图像数据载入TextureImage[0]。我们将用它来创建3个纹理。下面的行告诉OpenGL我们要创建三个纹理,它们将存放在texture[0], texture[1] 和 texture[2] 中。
         */
        glGenTextures(3, &texture[0]);                    // 创建纹理

        /**
         * 第六课中我们使用了线性滤波的纹理贴图。这需要机器有相当高的处理能力,但它们看起来很不错。这一课中,我们接着要创建的第一种纹理使用 GL_NEAREST方式。
         * 从原理上讲,这种方式没有真正进行滤波。它只占用很小的处理能力,看起来也很差。唯一的好处是这样我们的工程在很快和很慢的机器上都可以正常运行。
         * 您会注意到我们在 MIN 和 MAG 时都采用了GL_NEAREST,你可以混合使用 GL_NEAREST 和 GL_LINEAR。纹理看起来效果会好些,但我们更关心速度,所以全采用低质量贴图。
         * MIN_FILTER在图像绘制时小于贴图的原始尺寸时采用。MAG_FILTER在图像绘制时大于贴图的原始尺寸时采用。
         */
        // 创建 Nearest 滤波贴图
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     TextureImage->data);

        /**
         * The next texture we build is the same type of texture we used in tutorial six. Linear filtered.
         * The only thing that has changed is that we are storing this texture in texture[1] instead of texture[0] because it's our second texture.
         * If we stored it in texture[0] like above, it would overwrite the GL_NEAREST texture (the first texture).
         */
        // 创建线性滤波纹理
        glBindTexture(GL_TEXTURE_2D, texture[1]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     TextureImage->data);

        /**
         * 下面是创建纹理的新方法。 Mipmapping!您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失。刚才还很不错的图案变得很难看。
         * 当您告诉OpenGL创建一个 mipmapped的纹理后,OpenGL将尝试创建不同尺寸的高质量纹理。
         * 当您向屏幕绘制一个 mipmapped纹理的时候,OpenGL将选择它已经创建的外观最佳的纹理(带有更多细节)来绘制,而不仅仅是缩放原先的图像(这将导致细节丢失)。
         * 我曾经说过有办法可以绕过OpenGL对纹理宽度和高度所加的限制——64、128、256,等等。办法就是 gluBuild2DMipmaps。据我的发现,您可以使用任意的位图来创建纹理。
         * OpenGL将自动将它缩放到正常的大小。因为是第三个纹理,我们将它存到texture[2]。这样本课中的三个纹理全都创建好了。
         */
        // 创建 MipMapped 纹理
        glBindTexture(GL_TEXTURE_2D, texture[2]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

        /**
         * 下面一行生成 mipmapped 纹理。我们使用三种颜色(红,绿,蓝)来生成一个2D纹理。TextureImage[0]->sizeX 是位图宽度,extureImage[0]->sizeY 是位图高度,GL_RGB意味着我们依次使用RGB色彩。
         * GL_UNSIGNED_BYTE 意味着纹理数据的单位是字节。TextureImage[0]->data指向我们创建纹理所用的位图。
         */
        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage->sizeX, TextureImage->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
                          TextureImage->data);
    }
    /**
     * 现在我们释放前面用来存放位图数据的内存。我们先查看位图数据是否存放在处。如果是的话,再查看数据是否已经存储。如果已经存储的话,删了它。接着再释放 TextureImage[0] 图像结构以保证所有的内存都能释放。
     */
    if (TextureImage)                            // 纹理是否存在
    {
        if (TextureImage->data)                    // 纹理图像是否存在
        {
            free(TextureImage->data);                // 释放纹理图像占用的内存
        }

        free(TextureImage);                        // 释放图像结构
    }
    /**
     * 最后返回状态变量。如果一切OK,变量 Status 的值为 TRUE 。否则为 FALSE 。
     */
    return Status;
}


/**
 * 下面的代码的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您没有使用全屏模式)。
 * 甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运行一次--在程序开始时设置我们的透视图。
 * OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。
 * @param width
 * @param height
 * @return
 */
GLvoid Lesson16::ReSizeGLScene(GLsizei width, GLsizei height)                // 重置OpenGL窗口大小
{
    if (height == 0)                                // 防止被零除
    {
        height = 1;                            // 将Height设为1
    }

    glViewport(0, 0, width, height);                    // 重置当前的视口
    /**
     * 下面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。
     * 此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。
     *
     * glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projection matrix(投影矩阵)。投影矩阵负责为我们的场景增加透视。
     * glLoadIdentity()近似于重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。
     *
     * glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。
     */
    glMatrixMode(GL_PROJECTION);                        // 选择投影矩阵
    glLoadIdentity();                            // 重置投影矩阵

    // 设置视口的大小
    gluPerspective(45.0f, (GLfloat) width / (GLfloat) height, 0.1f, 100.0f);

    glMatrixMode(GL_MODELVIEW);                        // 选择模型观察矩阵
    glLoadIdentity();                            // 重置模型观察矩阵
}

/**
 * 接下的代码段中,我们将对OpenGL进行所有的设置。我们将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑),等等。这个例程直到OpenGL窗口创建之后才会被调用。
 * if (!LoadGLTextures()) 这行代码调用上面讲的子例程载入位图并生成纹理。如果因为任何原因 LoadGLTextures() 调用失败,接着的一行返回FALSE。如果一切OK,并且纹理创建好了,我们启用2D纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 * @return
 */
int Lesson16::InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())                            // 调用纹理载入子例程
    {
        return FALSE;                            // 如果未能载入,返回FALSE
    }
    glEnable(GL_TEXTURE_2D);                        // 启用纹理映射
    /**
     * 下一行启用smooth shading(阴影平滑)。阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑。
     */
    glShadeModel(GL_SMOOTH);                        // 启用阴影平滑
    /**
     * 下一行设置清除屏幕时所用的颜色。如果您对色彩的工作原理不清楚的话,我快速解释一下。
     * 色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。
     * glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。
     *
     * 通过混合三种原色(红、绿、蓝),您可以得到不同的色彩。因此,当您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您将用亮蓝色来清除屏幕。
     * 如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,您将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。
     * 要得到白色背景,您应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,您该将所有的颜色设为最暗(0.0f)。
     */
    glClearColor(0.5f,0.5f,0.5f,1.0f);			// 设置背景的颜色为雾气的颜色

    /**
     * 建立雾的过滤模式。之前我们声明了数组fogMode,它保存了值GL_EXP, GL_EXP2, and GL_LINEAR。现在是使用他们的时候了。
     * GL_EXP - 充满整个屏幕的基本渲染的雾。它能在较老的PC上工作,因此并不是特别像雾。
     * GL_EXP2 - 比GL_EXP更进一步。它也是充满整个屏幕,但它使屏幕看起来更有深度。
     * GL_LINEAR - 最好的渲染模式。物体淡入淡出的效果更自然。
     */
    glFogi(GL_FOG_MODE, fogMode[fogfilter]);		// 设置雾气的模式
    glFogfv(GL_FOG_COLOR, fogColor);			// 设置雾的颜色
    /**
     * 增加数字会让雾更密,减少它则雾更稀。
     */
    glFogf(GL_FOG_DENSITY, 0.35f);			// 设置雾的密度
    /**
     * gl_dont_care - 由OpenGL决定使用何种雾效(对每个顶点还是每个像素执行雾效计算)和一个未知的公式(?)
     * gl_nicest - 对每个像素执行雾效计算(效果好)
     * gl_fastest - 对每个顶点执行雾效计算 (更快,但效果不如上面的好)
     */
    glHint(GL_FOG_HINT, GL_DONT_CARE);			// 设置系统如何计算雾气
    glFogf(GL_FOG_START, 1.0f);				// 雾气的开始位置
    glFogf(GL_FOG_END, 5.0f);				// 雾气的结束位置
    glEnable(GL_FOG);					// 使用雾气

    /**
     * 接下来的三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
     * 我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。
     * 这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。
     */
    glClearDepth(1.0f);                            // 设置深度缓存
    glEnable(GL_DEPTH_TEST);                        // 启用深度测试
    glDepthFunc(GL_LEQUAL);                            // 所作深度测试的类型
    /**
     * 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
     */
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // 告诉系统对透视进行修正

    /**
     * 现在开始设置光源。下面下面一行设置环境光的发光量,光源light1开始发光。这一课的开始处我们我们将环境光的发光量存放在LightAmbient数组中。
     * 现在我们就使用此数组(半亮度环境光)。在int InitGL(GLvoid)函数中添加下面的代码。
     */
    glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);                // 设置环境光

    /**
     * 接下来我们设置漫射光的发光量。它存放在LightDiffuse数组中(全亮度白光)。
     */
    glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);                // 设置漫射光

    /**
     * 然后设置光源的位置。位置存放在 LightPosition 数组中(正好位于木箱前面的中心,X-0.0f,Y-0.0f,Z方向移向观察者2个单位<位于屏幕外面>)。
     */
    glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);            // 设置光源位置

    /**
     * 最后,我们启用一号光源。我们还没有启用GL_LIGHTING,所以您看不见任何光线。记住:只对光源进行设置、定位、甚至启用,光源都不会工作。除非我们启用GL_LIGHTING。
     */
    glEnable(GL_LIGHT1);                            // 启用一号光源
    glEnable(GL_LIGHTING);                      // 启用光源
    return TRUE;                                // 初始化 OK
}

/**
 * 下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。
 * 如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。
 * @return
 */
int Lesson16::DrawGLScene(GLvoid)                                // 从这里开始进行所有的绘制
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // 清除屏幕和深度缓存
    glLoadIdentity();                            // 重置当前的模型观察矩阵
    /**
     * 下三行代码放置并旋转贴图立方体。glTranslatef(0.0f,0.0f,z)将立方体沿着Z轴移动Z单位。glRotatef(xrot,1.0f,0.0f,0.0f)将立方体绕X轴旋转xrot。glRotatef(yrot,0.0f,1.0f,0.0f)将立方体绕Y轴旋转yrot。
     */
    glTranslatef(0.0f, 0.0f, z);                        // 移入/移出屏幕 z 个单位

    glRotatef(xrot, 1.0f, 0.0f, 0.0f);                        // 绕X轴旋转
    glRotatef(yrot, 0.0f, 1.0f, 0.0f);                        // 绕Y轴旋转

    /**
     * 下一行与我们在第六课中的类似。有所不同的是,这次我们绑定的纹理是texture[filter],而不是上一课中的texture[0]。
     * 任何时候,我们按下F键,filter 的值就会增加。如果这个数值大于2,变量filter 将被重置为0。程序初始时,变量filter 的值也将设为0。使用变量filter 我们就可以选择三种纹理中的任意一种。
     */
    glBindTexture(GL_TEXTURE_2D, texture[filter]);                // 选择由filter决定的纹理

    glBegin(GL_QUADS);                            // 开始绘制四边形

    /**
     * glNormal3f是这一课的新东西。Normal就是法线的意思,所谓法线是指经过面(多边形)上的一点且垂直于这个面(多边形)的直线。
     * 使用光源的时候必须指定一条法线。法线告诉OpenGL这个多边形的朝向,并指明多边形的正面和背面。如果没有指定法线,什么怪事情都可能发生:不该照亮的面被照亮了,多边形的背面也被照亮....。
     * 对了,法线应该指向多边形的外侧。看着木箱的前面您会注意到法线与Z轴正向同向。这意味着法线正指向观察者-您自己。这正是我们所希望的。对于木箱的背面,也正如我们所要的,法线背对着观察者。
     * 如果立方体沿着X或Y轴转个180度的话,前侧面的法线仍然朝着观察者,背面的法线也还是背对着观察者。换句话说,不管是哪个面,只要它朝着观察者这个面的法线就指向观察者。
     * 由于光源紧邻观察者,任何时候法线对着观察者时,这个面就会被照亮。并且法线越朝着光源,就显得越亮一些。如果您把观察点放到立方体内部,你就会法线里面一片漆黑。因为法线是向外指的。
     * 如果立方体内部没有光源的话,当然是一片漆黑。
     */
    // 前侧面
    glNormal3f(0.0f, 0.0f, 1.0f);                    // 法线指向观察者
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    // 后侧面
    glNormal3f(0.0f, 0.0f, -1.0f);                    // 法线背向观察者
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    // 顶面
    glNormal3f(0.0f, 1.0f, 0.0f);                    // 法线向上
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    // 底面
    glNormal3f(0.0f, -1.0f, 0.0f);                    // 法线朝下
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    // 右侧面
    glNormal3f(1.0f, 0.0f, 0.0f);                    // 法线朝右
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    // 左侧面
    glNormal3f(-1.0f, 0.0f, 0.0f);                    // 法线朝左
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glEnd();                                // 四边形绘制结束

    /**
     * 下两行代码将xot和yrot的旋转值分别增加xspeed和yspeed个单位。xspeed和yspeed的值越大,立方体转得就越快。
     */
    xrot += xspeed;                                // xrot 增加 xspeed 单位
    yrot += yspeed;                                // yrot 增加 yspeed 单位
    return TRUE;                                //  一切 OK
}
复制代码

文字纹理

#define TRUE 1
#define FALSE 0

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson17::LoadBMP(const char *filename,
                            AUX_RGBImageRec *texture_image) {
    FREE_IMAGE_FORMAT fifmt = FreeImage_GetFileType(filename, 0);
    FIBITMAP *dib = FreeImage_Load(fifmt, filename, 0);
    dib = FreeImage_ConvertTo24Bits(dib);
    int width = FreeImage_GetWidth(dib);
    int height = FreeImage_GetHeight(dib);
    BYTE *pixels = (BYTE *) FreeImage_GetBits(dib);
    int pix = 0;
    if (texture_image == NULL)
        return FALSE;
    texture_image->data = (BYTE *) malloc(width * height * 3);
    texture_image->sizeX = width;
    texture_image->sizeY = height;
    for (pix = 0; pix < width * height; pix++) {
        texture_image->data[pix * 3 + 0] = pixels[pix * 3 + 2];
        texture_image->data[pix * 3 + 1] = pixels[pix * 3 + 1];
        texture_image->data[pix * 3 + 2] = pixels[pix * 3 + 0];
    }
    FreeImage_Unload(dib);
    return TRUE;
}

/**
 * 下一部分代码载入位图(调用上面的代码)并转换成纹理。现在载入一个位图,并用它创建三种不同的纹理。这段代码调用前面的代码载入位图,并将其转换成3个纹理。Status 变量跟踪纹理是否已载入并被创建了。
 * @return
 */
int Lesson17::LoadGLTextures() {
    /**
     * 然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。 Status 缺省设为 FALSE (表示没有载入或创建任何东东)。
     */
    int Status = FALSE;                            // 状态指示器
    /**
     * 现在我们创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。
     */
    AUX_RGBImageRec *TextureImage1;                    // 创建纹理的存储空间
    TextureImage1 = (AUX_RGBImageRec *) malloc(sizeof(AUX_RGBImageRec));

    AUX_RGBImageRec *TextureImage2;                    // 创建纹理的存储空间
    TextureImage2 = (AUX_RGBImageRec *) malloc(sizeof(AUX_RGBImageRec));
    /**
     * 现在载入位图,并将其转换为纹理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 调用 LoadBMP() 的代码。
     * 载入 Data 目录下的 NeHe.bmp 位图文件。如果一切正常,图像数据将存放在 TextureImage[0] 中, Status 被设为 TRUE ,然后我们开始创建纹理。
     */
    // 载入位图,检查有无错误,如果位图没找到则退出
    if (LoadBMP("/home/zpw/Documents/CLionProjects/opengl_study/Data/Font.bmp", TextureImage1) &&
        // 载入字体图像
        LoadBMP("/home/zpw/Documents/CLionProjects/opengl_study/Data/Bumps.bmp", TextureImage2))            // 载入纹理图像
    {
        Status = TRUE;                            // 将 Status 设为 TRUE
        /**
         * 如果你用1替换2,那么将只创建一个纹理,第二个纹理将显示为全白。如果你用3替换2,你的程序可能崩溃!
         * 你应该只调用glGenTextures()一次。调用glGenTextures()后你应该创建你的所有纹理。我曾见过有人在每创建一个纹理前都加上一行glGenTextures()。
         * 这通常导致新建的纹理覆盖了你之前创建的。决定你需要创建多少个纹理是个好主意,调用glGenTextures()一次,然后创建所有的纹理。
         * 把glGenTextures()放进循环是不明智的,除非你有自己的理由。
         */
        glGenTextures(2, &texture[0]);                    // 创建纹理

        // 生成所有纹理
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage1->sizeX, TextureImage1->sizeY, 0, GL_RGB,
                     GL_UNSIGNED_BYTE, TextureImage1->data);

        glBindTexture(GL_TEXTURE_2D, texture[1]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage2->sizeX, TextureImage2->sizeY, 0, GL_RGB,
                     GL_UNSIGNED_BYTE, TextureImage2->data);
    }
    /**
     * 现在我们释放前面用来存放位图数据的内存。我们先查看位图数据是否存放在处。如果是的话,再查看数据是否已经存储。如果已经存储的话,删了它。接着再释放 TextureImage[0] 图像结构以保证所有的内存都能释放。
     */
    if (TextureImage1)                            // 纹理是否存在
    {
        if (TextureImage1->data)                    // 纹理图像是否存在
        {
            free(TextureImage1->data);                // 释放纹理图像占用的内存
        }

        free(TextureImage1);                        // 释放图像结构
    }

    if (TextureImage2)                            // 纹理是否存在
    {
        if (TextureImage2->data)                    // 纹理图像是否存在
        {
            free(TextureImage2->data);                // 释放纹理图像占用的内存
        }

        free(TextureImage2);                        // 释放图像结构
    }
    /**
     * 最后返回状态变量。如果一切OK,变量 Status 的值为 TRUE 。否则为 FALSE 。
     */
    return Status;
}

GLvoid Lesson17::BuildFont(GLvoid) {
    float cx;                                            // Holds Our X Character Coord
    float cy;                                            // Holds Our Y Character Coord

    base = glGenLists(256);                                // Creating 256 Display Lists
    glBindTexture(GL_TEXTURE_2D, texture[0]);            // Select Our Font Texture
    for (loop = 0; loop < 256; loop++)                        // Loop Through All 256 Lists
    {
        cx = float(loop % 16) / 16.0f;                        // X Position Of Current Character
        cy = float(loop / 16) / 16.0f;                        // Y Position Of Current Character

        glNewList(base + loop, GL_COMPILE);                // Start Building A List
        glBegin(GL_QUADS);                            // Use A Quad For Each Character
        glTexCoord2f(cx, 1 - cy - 0.0625f);            // Texture Coord (Bottom Left)
        glVertex2i(0, 0);                        // Vertex Coord (Bottom Left)
        glTexCoord2f(cx + 0.0625f, 1 - cy - 0.0625f);    // Texture Coord (Bottom Right)
        glVertex2i(16, 0);                        // Vertex Coord (Bottom Right)
        glTexCoord2f(cx + 0.0625f, 1 - cy);            // Texture Coord (Top Right)
        glVertex2i(16, 16);                        // Vertex Coord (Top Right)
        glTexCoord2f(cx, 1 - cy);                    // Texture Coord (Top Left)
        glVertex2i(0, 16);                        // Vertex Coord (Top Left)
        glEnd();                                    // Done Building Our Quad (Character)
        glTranslated(10, 0, 0);                        // Move To The Right Of The Character
        glEndList();                                    // Done Building The Display List
    }                                                    // Loop Until All 256 Are Built
}

/**
 * glPrint()有三个参数。第一个是屏幕上x轴上的位置(从左至右的位置),下一个是y轴上的位置(从下到上...0是底部,越往上越大)。
 * 然后是字符串(我们想打印的文字),最后是一个叫做set的变量。如果你看过Giuseppe D'Agata制作的位图,你会注意到有两个不同的字符集。
 * 第一个字符集是普通的,第二个是斜体的。如果set为0,第一个字符集被选中。若set为1则选择第二个字符集。
 */
GLvoid Lesson17::glPrint(GLint x, GLint y, char *string, int set)                // 绘制字符
{
    if (set > 1) {
        set = 1;
    }
    glBindTexture(GL_TEXTURE_2D, texture[0]);            // Select Our Font Texture
    glDisable(GL_DEPTH_TEST);                            // Disables Depth Testing
    glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
    glPushMatrix();                                        // Store The Projection Matrix
    glLoadIdentity();                                    // Reset The Projection Matrix
    glOrtho(0, 640, 0, 480, -1, 1);                            // Set Up An Ortho Screen
    glMatrixMode(GL_MODELVIEW);                            // Select The Modelview Matrix
    glPushMatrix();                                        // Store The Modelview Matrix
    glLoadIdentity();                                    // Reset The Modelview Matrix
    glTranslated(x, y, 0);                                // Position The Text (0,0 - Bottom Left)
    glListBase(base - 32 + (128 * set));                        // Choose The Font Set (0 or 1)
    glCallLists(strlen(string), GL_UNSIGNED_BYTE, string);// Write The Text To The Screen
    glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
    glPopMatrix();                                        // Restore The Old Projection Matrix
    glMatrixMode(GL_MODELVIEW);                            // Select The Modelview Matrix
    glPopMatrix();                                        // Restore The Old Projection Matrix
    glEnable(GL_DEPTH_TEST);                            // Enables Depth Testing
}

/**
 * 下面的代码的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您没有使用全屏模式)。
 * 甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运行一次--在程序开始时设置我们的透视图。
 * OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。
 * @param width
 * @param height
 * @return
 */
GLvoid Lesson17::ReSizeGLScene(GLsizei width, GLsizei height)                // 重置OpenGL窗口大小
{
    if (height == 0)                                        // Prevent A Divide By Zero By
    {
        height = 1;                                        // Making Height Equal One
    }
    glViewport(0, 0, width, height);                        // Reset The Current Viewport
    glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
    glLoadIdentity();                                    // Reset The Projection Matrix
    gluPerspective(45.0f, (GLfloat) width / (GLfloat) height, 0.1f, 100.0f);    // Calculate Window Aspect Ratio
    glMatrixMode(GL_MODELVIEW);                            // Select The Modelview Matrix
    glLoadIdentity();                                    // Reset The Modelview Matrix
}

/**
 * 接下的代码段中,我们将对OpenGL进行所有的设置。我们将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑),等等。这个例程直到OpenGL窗口创建之后才会被调用。
 * if (!LoadGLTextures()) 这行代码调用上面讲的子例程载入位图并生成纹理。如果因为任何原因 LoadGLTextures() 调用失败,接着的一行返回FALSE。如果一切OK,并且纹理创建好了,我们启用2D纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 * @return
 */
int Lesson17::InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())                                // Jump To Texture Loading Routine
    {
        return FALSE;                                    // If Texture Didn't Load Return FALSE
    }
    BuildFont();                                        // Build The Font
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                // Clear The Background Color To Black
    glClearDepth(1.0);                                    // Enables Clearing Of The Depth Buffer
    glDepthFunc(GL_LEQUAL);                                // The Type Of Depth Test To Do
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);                    // Select The Type Of Blending
    glShadeModel(GL_SMOOTH);                            // Enables Smooth Color Shading
    glEnable(GL_TEXTURE_2D);                            // Enable 2D Texture Mapping
    return TRUE;                                // 初始化 OK
}

/**
 * 下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。
 * 如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。
 * @return
 */
int Lesson17::DrawGLScene(GLvoid)                                // 从这里开始进行所有的绘制
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Clear The Screen And The Depth Buffer
    glLoadIdentity();                                    // Reset The Modelview Matrix
    glBindTexture(GL_TEXTURE_2D, texture[1]);            // Select Our Second Texture
    glTranslatef(0.0f, 0.0f, -5.0f);                        // Move Into The Screen 5 Units
    glRotatef(45.0f, 0.0f, 0.0f, 1.0f);                    // Rotate On The Z Axis 45 Degrees (Clockwise)
    glRotatef(cnt1 * 30.0f, 1.0f, 1.0f, 0.0f);                // Rotate On The X & Y Axis By cnt1 (Left To Right)
    glDisable(GL_BLEND);                                // Disable Blending Before We Draw In 3D
    glColor3f(1.0f, 1.0f, 1.0f);                            // Bright White
    glBegin(GL_QUADS);                                    // Draw Our First Texture Mapped Quad
    glTexCoord2d(0.0f, 0.0f);                        // First Texture Coord
    glVertex2f(-1.0f, 1.0f);                        // First Vertex
    glTexCoord2d(1.0f, 0.0f);                        // Second Texture Coord
    glVertex2f(1.0f, 1.0f);                        // Second Vertex
    glTexCoord2d(1.0f, 1.0f);                        // Third Texture Coord
    glVertex2f(1.0f, -1.0f);                        // Third Vertex
    glTexCoord2d(0.0f, 1.0f);                        // Fourth Texture Coord
    glVertex2f(-1.0f, -1.0f);                        // Fourth Vertex
    glEnd();                                            // Done Drawing The First Quad
    glRotatef(90.0f, 1.0f, 1.0f, 0.0f);                    // Rotate On The X & Y Axis By 90 Degrees (Left To Right)
    glBegin(GL_QUADS);                                    // Draw Our Second Texture Mapped Quad
    glTexCoord2d(0.0f, 0.0f);                        // First Texture Coord
    glVertex2f(-1.0f, 1.0f);                        // First Vertex
    glTexCoord2d(1.0f, 0.0f);                        // Second Texture Coord
    glVertex2f(1.0f, 1.0f);                        // Second Vertex
    glTexCoord2d(1.0f, 1.0f);                        // Third Texture Coord
    glVertex2f(1.0f, -1.0f);                        // Third Vertex
    glTexCoord2d(0.0f, 1.0f);                        // Fourth Texture Coord
    glVertex2f(-1.0f, -1.0f);                        // Fourth Vertex
    glEnd();                                            // Done Drawing Our Second Quad
    glEnable(GL_BLEND);                                    // Enable Blending

    glLoadIdentity();                                    // Reset The View
    // Pulsing Colors Based On Text Position
    glColor3f(1.0f * float(cos(cnt1)), 1.0f * float(sin(cnt2)), 1.0f - 0.5f * float(cos(cnt1 + cnt2)));
    glPrint(int((280 + 250 * cos(cnt1))), int(235 + 200 * sin(cnt2)), "NeHe", 0);        // Print GL Text To The Screen

    glColor3f(1.0f * float(sin(cnt2)), 1.0f - 0.5f * float(cos(cnt1 + cnt2)), 1.0f * float(cos(cnt1)));
    glPrint(int((280 + 230 * cos(cnt2))), int(235 + 200 * sin(cnt1)), "OpenGL", 1);    // Print GL Text To The Screen

    glColor3f(0.0f, 0.0f, 1.0f);                            // Set Color To Blue
    glPrint(int(240 + 200 * cos((cnt2 + cnt1) / 5)), 2, "Giuseppe D'Agata", 0);

    glColor3f(1.0f, 1.0f, 1.0f);                            // Set Color To White
    glPrint(int(242 + 200 * cos((cnt2 + cnt1) / 5)), 2, "Giuseppe D'Agata", 0);

    cnt1 += 0.01f;                                        // Increase The First Counter
    cnt2 += 0.0081f;                                        // Increase The Second Counter
    return TRUE;                                //  一切 OK
}
复制代码

二次几何体

#define TRUE 1
#define FALSE 0

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson18::LoadBmp(const char *filename,
                            AUX_RGBImageRec *texture_image) {
    FREE_IMAGE_FORMAT fifmt = FreeImage_GetFileType(filename, 0);
    FIBITMAP *dib = FreeImage_Load(fifmt, filename, 0);
    dib = FreeImage_ConvertTo24Bits(dib);
    int width = FreeImage_GetWidth(dib);
    int height = FreeImage_GetHeight(dib);
    BYTE *pixels = (BYTE *) FreeImage_GetBits(dib);
    int pix = 0;
    if (texture_image == NULL)
        return FALSE;
    texture_image->data = (BYTE *) malloc(width * height * 3);
    texture_image->sizeX = width;
    texture_image->sizeY = height;
    for (pix = 0; pix < width * height; pix++) {
        texture_image->data[pix * 3 + 0] = pixels[pix * 3 + 2];
        texture_image->data[pix * 3 + 1] = pixels[pix * 3 + 1];
        texture_image->data[pix * 3 + 2] = pixels[pix * 3 + 0];
    }
    FreeImage_Unload(dib);
    return TRUE;
}

/**
 * 下一部分代码载入位图(调用上面的代码)并转换成纹理。现在载入一个位图,并用它创建三种不同的纹理。这段代码调用前面的代码载入位图,并将其转换成3个纹理。Status 变量跟踪纹理是否已载入并被创建了。
 * @return
 */
int Lesson18::LoadGLTextures() {
    /**
     * 然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。 Status 缺省设为 FALSE (表示没有载入或创建任何东东)。
     */
    int Status = FALSE;                            // 状态指示器
    /**
     * 现在我们创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。
     */
    AUX_RGBImageRec *TextureImage;                    // 创建纹理的存储空间
    TextureImage = (AUX_RGBImageRec *) malloc(sizeof(AUX_RGBImageRec));
    /**
     * 现在载入位图,并将其转换为纹理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 调用 LoadBMP() 的代码。
     * 载入 Data 目录下的 NeHe.bmp 位图文件。如果一切正常,图像数据将存放在 TextureImage[0] 中, Status 被设为 TRUE ,然后我们开始创建纹理。
     */
    // 载入位图,检查有无错误,如果位图没找到则退出
    if (LoadBmp("/home/zpw/Documents/CLionProjects/opengl_study/Data/Wall.bmp", TextureImage)) {
        Status = TRUE;                            // 将 Status 设为 TRUE
        /**
         * 现在我们已经将图像数据载入TextureImage[0]。我们将用它来创建3个纹理。下面的行告诉OpenGL我们要创建三个纹理,它们将存放在texture[0], texture[1] 和 texture[2] 中。
         */
        glGenTextures(3, &texture[0]);                    // 创建纹理

        /**
         * 第六课中我们使用了线性滤波的纹理贴图。这需要机器有相当高的处理能力,但它们看起来很不错。这一课中,我们接着要创建的第一种纹理使用 GL_NEAREST方式。
         * 从原理上讲,这种方式没有真正进行滤波。它只占用很小的处理能力,看起来也很差。唯一的好处是这样我们的工程在很快和很慢的机器上都可以正常运行。
         * 您会注意到我们在 MIN 和 MAG 时都采用了GL_NEAREST,你可以混合使用 GL_NEAREST 和 GL_LINEAR。纹理看起来效果会好些,但我们更关心速度,所以全采用低质量贴图。
         * MIN_FILTER在图像绘制时小于贴图的原始尺寸时采用。MAG_FILTER在图像绘制时大于贴图的原始尺寸时采用。
         */
        // 创建 Nearest 滤波贴图
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     TextureImage->data);

        /**
         * The next texture we build is the same type of texture we used in tutorial six. Linear filtered.
         * The only thing that has changed is that we are storing this texture in texture[1] instead of texture[0] because it's our second texture.
         * If we stored it in texture[0] like above, it would overwrite the GL_NEAREST texture (the first texture).
         */
        // 创建线性滤波纹理
        glBindTexture(GL_TEXTURE_2D, texture[1]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     TextureImage->data);

        /**
         * 下面是创建纹理的新方法。 Mipmapping!您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失。刚才还很不错的图案变得很难看。
         * 当您告诉OpenGL创建一个 mipmapped的纹理后,OpenGL将尝试创建不同尺寸的高质量纹理。
         * 当您向屏幕绘制一个 mipmapped纹理的时候,OpenGL将选择它已经创建的外观最佳的纹理(带有更多细节)来绘制,而不仅仅是缩放原先的图像(这将导致细节丢失)。
         * 我曾经说过有办法可以绕过OpenGL对纹理宽度和高度所加的限制——64、128、256,等等。办法就是 gluBuild2DMipmaps。据我的发现,您可以使用任意的位图来创建纹理。
         * OpenGL将自动将它缩放到正常的大小。因为是第三个纹理,我们将它存到texture[2]。这样本课中的三个纹理全都创建好了。
         */
        // 创建 MipMapped 纹理
        glBindTexture(GL_TEXTURE_2D, texture[2]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

        /**
         * 下面一行生成 mipmapped 纹理。我们使用三种颜色(红,绿,蓝)来生成一个2D纹理。TextureImage[0]->sizeX 是位图宽度,extureImage[0]->sizeY 是位图高度,GL_RGB意味着我们依次使用RGB色彩。
         * GL_UNSIGNED_BYTE 意味着纹理数据的单位是字节。TextureImage[0]->data指向我们创建纹理所用的位图。
         */
        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage->sizeX, TextureImage->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
                          TextureImage->data);
    }
    /**
     * 现在我们释放前面用来存放位图数据的内存。我们先查看位图数据是否存放在处。如果是的话,再查看数据是否已经存储。如果已经存储的话,删了它。接着再释放 TextureImage[0] 图像结构以保证所有的内存都能释放。
     */
    if (TextureImage)                            // 纹理是否存在
    {
        if (TextureImage->data)                    // 纹理图像是否存在
        {
            free(TextureImage->data);                // 释放纹理图像占用的内存
        }

        free(TextureImage);                        // 释放图像结构
    }
    /**
     * 最后返回状态变量。如果一切OK,变量 Status 的值为 TRUE 。否则为 FALSE 。
     */
    return Status;
}


/**
 * 下面的代码的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您没有使用全屏模式)。
 * 甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运行一次--在程序开始时设置我们的透视图。
 * OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。
 * @param width
 * @param height
 * @return
 */
GLvoid Lesson18::ReSizeGLScene(GLsizei width, GLsizei height)                // 重置OpenGL窗口大小
{
    if (height == 0)                                // 防止被零除
    {
        height = 1;                            // 将Height设为1
    }

    glViewport(0, 0, width, height);                    // 重置当前的视口
    /**
     * 下面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。
     * 此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。
     *
     * glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projection matrix(投影矩阵)。投影矩阵负责为我们的场景增加透视。
     * glLoadIdentity()近似于重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。
     *
     * glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。
     */
    glMatrixMode(GL_PROJECTION);                        // 选择投影矩阵
    glLoadIdentity();                            // 重置投影矩阵

    // 设置视口的大小
    gluPerspective(45.0f, (GLfloat) width / (GLfloat) height, 0.1f, 100.0f);

    glMatrixMode(GL_MODELVIEW);                        // 选择模型观察矩阵
    glLoadIdentity();                            // 重置模型观察矩阵
}

/**
 * 接下的代码段中,我们将对OpenGL进行所有的设置。我们将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑),等等。这个例程直到OpenGL窗口创建之后才会被调用。
 * if (!LoadGLTextures()) 这行代码调用上面讲的子例程载入位图并生成纹理。如果因为任何原因 LoadGLTextures() 调用失败,接着的一行返回FALSE。如果一切OK,并且纹理创建好了,我们启用2D纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 * @return
 */
int Lesson18::InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())                            // 调用纹理载入子例程
    {
        return FALSE;                            // 如果未能载入,返回FALSE
    }
    glEnable(GL_TEXTURE_2D);                        // 启用纹理映射
    /**
     * 下一行启用smooth shading(阴影平滑)。阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑。
     */
    glShadeModel(GL_SMOOTH);                        // 启用阴影平滑
    /**
     * 下一行设置清除屏幕时所用的颜色。如果您对色彩的工作原理不清楚的话,我快速解释一下。
     * 色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。
     * glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。
     *
     * 通过混合三种原色(红、绿、蓝),您可以得到不同的色彩。因此,当您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您将用亮蓝色来清除屏幕。
     * 如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,您将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。
     * 要得到白色背景,您应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,您该将所有的颜色设为最暗(0.0f)。
     */
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                    // 黑色背景
    /**
     * 接下来的三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
     * 我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。
     * 这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。
     */
    glClearDepth(1.0f);                            // 设置深度缓存
    glEnable(GL_DEPTH_TEST);                        // 启用深度测试
    glDepthFunc(GL_LEQUAL);                            // 所作深度测试的类型
    /**
     * 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
     */
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // 告诉系统对透视进行修正

    /**
     * 现在开始设置光源。下面下面一行设置环境光的发光量,光源light1开始发光。这一课的开始处我们我们将环境光的发光量存放在LightAmbient数组中。
     * 现在我们就使用此数组(半亮度环境光)。在int InitGL(GLvoid)函数中添加下面的代码。
     */
    glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);                // 设置环境光

    /**
     * 接下来我们设置漫射光的发光量。它存放在LightDiffuse数组中(全亮度白光)。
     */
    glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);                // 设置漫射光

    /**
     * 然后设置光源的位置。位置存放在 LightPosition 数组中(正好位于木箱前面的中心,X-0.0f,Y-0.0f,Z方向移向观察者2个单位<位于屏幕外面>)。
     */
    glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);            // 设置光源位置

    /**
     * 最后,我们启用一号光源。我们还没有启用GL_LIGHTING,所以您看不见任何光线。记住:只对光源进行设置、定位、甚至启用,光源都不会工作。除非我们启用GL_LIGHTING。
     */
    glEnable(GL_LIGHT1);                            // 启用一号光源
    glEnable(GL_LIGHTING);                      // 启用光源

    /**
     * 第一行代码将初始化二次曲面并且创建一个指向改二次曲面的指针,如果改二次曲面不能被创建的话,那么该指针就是NULL。
     * 第二行代码将在二次曲面的表面创建平滑的法向量,这样当灯光照上去的时候将会好看些。另外一些可能的取值是:GLU_NONE和GLU_FLAT。最后我们使在二次曲面表面的纹理映射有效。
     */
    quadratic=gluNewQuadric();				// 创建二次几何体
    gluQuadricNormals(quadratic, GLU_SMOOTH);		// 使用平滑法线
    gluQuadricTexture(quadratic, GL_TRUE);		// 使用纹理
    return TRUE;                                // 初始化 OK
}

GLvoid Lesson18::glDrawCube()					// 绘制立方体
{
    glBegin(GL_QUADS);
    // 前面
    glNormal3f( 0.0f, 0.0f, 1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    // 后面
    glNormal3f( 0.0f, 0.0f,-1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    // 上面
    glNormal3f( 0.0f, 1.0f, 0.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    // 下面
    glNormal3f( 0.0f,-1.0f, 0.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    // 右面
    glNormal3f( 1.0f, 0.0f, 0.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    // 左面
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glEnd();
}

/**
 * 下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。
 * 如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。
 * @return
 */
int Lesson18::DrawGLScene(GLvoid)                                // 从这里开始进行所有的绘制
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear The Screen And The Depth Buffer
    glLoadIdentity();									// Reset The View
    glTranslatef(0.0f,0.0f,z);

    glRotatef(xrot,1.0f,0.0f,0.0f);
    glRotatef(yrot,0.0f,1.0f,0.0f);

    glBindTexture(GL_TEXTURE_2D, texture[filter]);

    switch(object)
    {
        case 0:
            glDrawCube();
            break;
        case 1:
            glTranslatef(0.0f,0.0f,-1.5f);					// Center The Cylinder
            gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);	// A Cylinder With A Radius Of 0.5 And A Height Of 2
            break;
        case 2:
            gluDisk(quadratic,0.5f,1.5f,32,32);				// Draw A Disc (CD Shape) With An Inner Radius Of 0.5, And An Outer Radius Of 2.  Plus A Lot Of Segments ;)
            break;
        case 3:
            gluSphere(quadratic,1.3f,32,32);				// Draw A Sphere With A Radius Of 1 And 16 Longitude And 16 Latitude Segments
            break;
        case 4:
            glTranslatef(0.0f,0.0f,-1.5f);					// Center The Cone
            gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32);	// A Cone With A Bottom Radius Of .5 And A Height Of 2
            break;
        case 5:
            part1+=p1;
            part2+=p2;

            if(part1>359)									// 360 Degrees
            {
                p1=0;
                part1=0;
                p2=1;
                part2=0;
            }
            if(part2>359)									// 360 Degrees
            {
                p1=1;
                p2=0;
            }
            gluPartialDisk(quadratic,0.5f,1.5f,32,32,part1,part2-part1);	// A Disk Like The One Before
            break;
    };

    xrot+=xspeed;
    yrot+=yspeed;
    return TRUE;                                //  一切 OK
}
复制代码

粒子系统

#define TRUE 1
#define FALSE 0

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson19::LoadBmp(const char *filename,
                            AUX_RGBImageRec *texture_image) {
    FREE_IMAGE_FORMAT fifmt = FreeImage_GetFileType(filename, 0);
    FIBITMAP *dib = FreeImage_Load(fifmt, filename, 0);
    dib = FreeImage_ConvertTo24Bits(dib);
    int width = FreeImage_GetWidth(dib);
    int height = FreeImage_GetHeight(dib);
    BYTE *pixels = (BYTE *) FreeImage_GetBits(dib);
    int pix = 0;
    if (texture_image == NULL)
        return FALSE;
    texture_image->data = (BYTE *) malloc(width * height * 3);
    texture_image->sizeX = width;
    texture_image->sizeY = height;
    for (pix = 0; pix < width * height; pix++) {
        texture_image->data[pix * 3 + 0] = pixels[pix * 3 + 2];
        texture_image->data[pix * 3 + 1] = pixels[pix * 3 + 1];
        texture_image->data[pix * 3 + 2] = pixels[pix * 3 + 0];
    }
    FreeImage_Unload(dib);
    return TRUE;
}

/**
 * 下一部分代码载入位图(调用上面的代码)并转换成纹理。
 * @return
 */
int Lesson19::LoadGLTextures() {
    /**
     * 然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。 Status 缺省设为 FALSE (表示没有载入或创建任何东东)。
     */
    int Status = FALSE;                            // 状态指示器
    /**
     * 现在我们创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。
     */
    AUX_RGBImageRec *TextureImage;                    // 创建纹理的存储空间
    TextureImage = (AUX_RGBImageRec *) malloc(sizeof(AUX_RGBImageRec));
    /**
     * 现在载入位图,并将其转换为纹理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 调用 LoadBMP() 的代码。
     * 载入 Data 目录下的 NeHe.bmp 位图文件。如果一切正常,图像数据将存放在 TextureImage[0] 中, Status 被设为 TRUE ,然后我们开始创建纹理。
     */
    // 载入位图,检查有无错误,如果位图没找到则退出
    if (LoadBmp("/home/zpw/Documents/CLionProjects/opengl_study/Data/Particle.bmp", TextureImage)) {
        Status = TRUE;                            // 将 Status 设为 TRUE
        /**
         * 现在使用中 TextureImage[0] 的数据创建纹理。第一行 glGenTextures(1, &texture[0]) 告诉OpenGL我们想生成一个纹理名字(如果您想载入多个纹理,加大数字)。
         * 值得注意的是,开始我们使用 GLuint texture[1] 来创建一个纹理的存储空间,您也许会认为第一个纹理就是存放在 &texture[1] 中的,但这是错的。
         * 正确的地址应该是 &texture[0] 。同样如果使用 GLuint texture[2] 的话,第二个纹理存放在 texture[1] 中。
         */
        /**
         * 第二行 glBindTexture(GL_TEXTURE_2D, texture[0]) 告诉OpenGL将纹理名字 texture[0] 绑定到纹理目标上。
         * 2D纹理只有高度(在 Y 轴上)和宽度(在 X 轴上)。主函数将纹理名字指派给纹理数据。本例中我们告知OpenGL, &texture[0] 处的内存已经可用。我们创建的纹理将存储在 &texture[0] 的 指向的内存区域。
         */
        glGenTextures(1, &texture[0]);                    // 创建纹理

        // 使用来自位图数据生成 的典型纹理
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        /**
         * 下来我们创建真正的纹理。下面一行告诉OpenGL此纹理是一个2D纹理 ( GL_TEXTURE_2D )。
         * 参数“0”代表图像的详细程度,通常就由它为零去了。参数三是数据的成分数。因为图像是由红色数据,绿色数据,蓝色数据三种组分组成。
         * TextureImage[0]->sizeX 是纹理的宽度。如果您知道宽度,您可以在这里填入,但计算机可以很容易的为您指出此值。
         * TextureImage[0]->sizey 是纹理的高度。参数零是边框的值,一般就是“0”。 GL_RGB 告诉OpenGL图像数据由红、绿、蓝三色数据组成。
         * GL_UNSIGNED_BYTE 意味着组成图像的数据是无符号字节类型的。最后... TextureImage[0]->data 告诉OpenGL纹理数据的来源。此例中指向存放在 TextureImage[0] 记录中的数据。
         */
        // 生成纹理
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage->sizeX, TextureImage->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     TextureImage->data);
        /**
         * 下面的两行告诉OpenGL在显示图像时,当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时OpenGL采用的滤波方式。
         * 通常这两种情况下我都采用 GL_LINEAR 。这使得纹理从很远处到离屏幕很近时都平滑显示。使用 GL_LINEAR 需要CPU和显卡做更多的运算。
         * 如果您的机器很慢,您也许应该采用 GL_NEAREST 。过滤的纹理在放大的时候,看起来斑驳的很『译者注:马赛克啦』。您也可以结合这两种滤波方式。在近处时使用 GL_LINEAR ,远处时 GL_NEAREST 。
         */
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    // 线形滤波
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    // 线形滤波
    }
    /**
     * 现在我们释放前面用来存放位图数据的内存。我们先查看位图数据是否存放在处。如果是的话,再查看数据是否已经存储。如果已经存储的话,删了它。接着再释放 TextureImage[0] 图像结构以保证所有的内存都能释放。
     */
    if (TextureImage)                            // 纹理是否存在
    {
        if (TextureImage->data)                    // 纹理图像是否存在
        {
            free(TextureImage->data);                // 释放纹理图像占用的内存
        }

        free(TextureImage);                        // 释放图像结构
    }
    /**
     * 最后返回状态变量。如果一切OK,变量 Status 的值为 TRUE 。否则为 FALSE 。
     */
    return Status;
}


/**
 * 下面的代码的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您没有使用全屏模式)。
 * 甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运行一次--在程序开始时设置我们的透视图。
 * OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。
 * @param width
 * @param height
 * @return
 */
GLvoid Lesson19::ReSizeGLScene(GLsizei width, GLsizei height)                // 重置OpenGL窗口大小
{
    if (height == 0)                                // 防止被零除
    {
        height = 1;                            // 将Height设为1
    }

    glViewport(0, 0, width, height);                    // 重置当前的视口
    /**
     * 下面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。
     * 此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。
     *
     * glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projection matrix(投影矩阵)。投影矩阵负责为我们的场景增加透视。
     * glLoadIdentity()近似于重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。
     *
     * glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。
     */
    glMatrixMode(GL_PROJECTION);                        // 选择投影矩阵
    glLoadIdentity();                            // 重置投影矩阵

    // 设置视口的大小
    gluPerspective(45.0f, (GLfloat) width / (GLfloat) height, 0.1f, 200.0f);

    glMatrixMode(GL_MODELVIEW);                        // 选择模型观察矩阵
    glLoadIdentity();                            // 重置模型观察矩阵
}

/**
 * 接下的代码段中,我们将对OpenGL进行所有的设置。我们将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑),等等。这个例程直到OpenGL窗口创建之后才会被调用。
 * if (!LoadGLTextures()) 这行代码调用上面讲的子例程载入位图并生成纹理。如果因为任何原因 LoadGLTextures() 调用失败,接着的一行返回FALSE。如果一切OK,并且纹理创建好了,我们启用2D纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 * @return
 */
int Lesson19::InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())                            // 调用纹理载入子例程
    {
        return FALSE;                            // 如果未能载入,返回FALSE
    }
    glShadeModel(GL_SMOOTH);                            // Enable Smooth Shading
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                    // Black Background
    glClearDepth(1.0f);                                    // Depth Buffer Setup
    // 我们使用光滑的阴影,清除背景为黑色,关闭深度测试,绑定并映射纹理.启用映射位图后我们选择粒子纹理。唯一的改变就是禁用深度测试和初始化粒子
    glDisable(GL_DEPTH_TEST);                            // Disable Depth Testing
    glEnable(GL_BLEND);                                    // Enable Blending
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);                    // Type Of Blending To Perform
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // Really Nice Perspective Calculations
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);                // Really Nice Point Smoothing
    glEnable(GL_TEXTURE_2D);                            // Enable Texture Mapping
    glBindTexture(GL_TEXTURE_2D, texture[0]);            // Select Our Texture

    /**
     * 下面代码初始化每个粒子.我们从活跃的粒子开始.如果粒子不活跃,它在荧屏上将不出现,无论它有多少life.当我们使粒子活跃之後,我们给它life.
     * 我怀疑给粒子生命和颜色渐变的是否是最好的方法,但当它运行一次后,效果很好!life满值是1.0f.这也给粒子完整的光亮.
     */
    for (loop = 0; loop < MAX_PARTICLES; loop++)                // Initials All The Textures
    {
        particle[loop].active = true;                                // Make All The Particles Active
        particle[loop].life = 1.0f;                                // Give All The Particles Full Life
        particle[loop].fade = float(rand() % 100) / 1000.0f + 0.003f;    // Random Fade Speed
        particle[loop].r = colors[loop * (12 / MAX_PARTICLES)][0];    // Select Red Rainbow Color
        particle[loop].g = colors[loop * (12 / MAX_PARTICLES)][1];    // Select Red Rainbow Color
        particle[loop].b = colors[loop * (12 / MAX_PARTICLES)][2];    // Select Red Rainbow Color
        particle[loop].xi = float((rand() % 50) - 26.0f) * 10.0f;        // Random Speed On X Axis
        particle[loop].yi = float((rand() % 50) - 25.0f) * 10.0f;        // Random Speed On Y Axis
        particle[loop].zi = float((rand() % 50) - 25.0f) * 10.0f;        // Random Speed On Z Axis
        particle[loop].xg = 0.0f;                                    // Set Horizontal Pull To Zero
        particle[loop].yg = -0.8f;                                // Set Vertical Pull Downward
        particle[loop].zg = 0.0f;                                    // Set Pull On Z Axis To Zero
    }
    return TRUE;                                // 初始化 OK
}

/**
 * 下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。
 * 如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。
 * @return
 */
int Lesson19::DrawGLScene(GLvoid)                                // 从这里开始进行所有的绘制
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        // Clear Screen And Depth Buffer
    glLoadIdentity();                                        // Reset The ModelView Matrix

    for (loop = 0; loop < MAX_PARTICLES; loop++)                    // Loop Through All The Particles
    {
        if (particle[loop].active)                            // If The Particle Is Active
        {
            float x = particle[loop].x;                        // Grab Our Particle X Position
            float y = particle[loop].y;                        // Grab Our Particle Y Position
            float z = particle[loop].z + zoom;                    // Particle Z Pos + Zoom

            // Draw The Particle Using Our RGB Values, Fade The Particle Based On It's Life
            glColor4f(particle[loop].r, particle[loop].g, particle[loop].b, particle[loop].life);

            glBegin(GL_TRIANGLE_STRIP);                        // Build Quad From A Triangle Strip
            glTexCoord2d(1, 1);
            glVertex3f(x + 0.5f, y + 0.5f, z); // Top Right
            glTexCoord2d(0, 1);
            glVertex3f(x - 0.5f, y + 0.5f, z); // Top Left
            glTexCoord2d(1, 0);
            glVertex3f(x + 0.5f, y - 0.5f, z); // Bottom Right
            glTexCoord2d(0, 0);
            glVertex3f(x - 0.5f, y - 0.5f, z); // Bottom Left
            glEnd();                                        // Done Building Triangle Strip

            particle[loop].x += particle[loop].xi / (slowdown * 1000);// Move On The X Axis By X Speed
            particle[loop].y += particle[loop].yi / (slowdown * 1000);// Move On The Y Axis By Y Speed
            particle[loop].z += particle[loop].zi / (slowdown * 1000);// Move On The Z Axis By Z Speed

            particle[loop].xi += particle[loop].xg;            // Take Pull On X Axis Into Account
            particle[loop].yi += particle[loop].yg;            // Take Pull On Y Axis Into Account
            particle[loop].zi += particle[loop].zg;            // Take Pull On Z Axis Into Account
            particle[loop].life -= particle[loop].fade;        // Reduce Particles Life By 'Fade'

            if (particle[loop].life < 0.0f)                    // If Particle Is Burned Out
            {
                particle[loop].life = 1.0f;                    // Give It New Life
                particle[loop].fade = float(rand() % 100) / 1000.0f + 0.003f;    // Random Fade Value
                particle[loop].x = 0.0f;                        // Center On X Axis
                particle[loop].y = 0.0f;                        // Center On Y Axis
                particle[loop].z = 0.0f;                        // Center On Z Axis
                particle[loop].xi = xspeed + float((rand() % 60) - 32.0f);    // X Axis Speed And Direction
                particle[loop].yi = yspeed + float((rand() % 60) - 30.0f);    // Y Axis Speed And Direction
                particle[loop].zi = float((rand() % 60) - 30.0f);    // Z Axis Speed And Direction
                particle[loop].r = colors[col][0];            // Select Red From Color Table
                particle[loop].g = colors[col][1];            // Select Green From Color Table
                particle[loop].b = colors[col][2];            // Select Blue From Color Table
            }
        }
    }
    return TRUE;                                //  一切 OK
}

复制代码
关注下面的标签,发现更多相似文章
评论