阅读 1

OpenGL NeHe on Linux 2

光照

#define TRUE 1
#define FALSE 0

/**
     * 接着设置用来创建光源的数组。我们将使用两种不同的光。第一种称为环境光。环境光来自于四面八方。所有场景中的对象都处于环境光的照射中。
     * 第二种类型的光源叫做漫射光。漫射光由特定的光源产生,并在您的场景中的对象表面上产生反射。处于漫射光直接照射下的任何对象表面都变得很亮,而几乎未被照射到的区域就显得要暗一些。
     * 这样在我们所创建的木板箱的棱边上就会产生的很不错的阴影效果。创建光源的过程和颜色的创建完全一致。前三个参数分别是RGB三色分量,最后一个是alpha通道参数。
     * 因此,下面的代码我们得到的是半亮(0.5f)的白色环境光。如果没有环境光,未被漫射光照到的地方会变得十分黑暗。
     * @return
     */
GLfloat LightAmbient[] = {0.5f, 0.5f, 0.5f, 1.0f};                // 环境光参数

/**
 * 下一行代码我们生成最亮的漫射光。所有的参数值都取成最大值1.0f。它将照在我们木板箱的前面,看起来挺好。
 * @return
 */
GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};                 // 漫射光参数

/**
 * 最后我们保存光源的位置。前三个参数和glTranslate中的一样。依次分别是XYZ轴上的位移。由于我们想要光线直接照射在木箱的正面,所以XY轴上的位移都是0.0f。
 * 第三个值是Z轴上的位移。为了保证光线总在木箱的前面,所以我们将光源的位置朝着观察者(就是您哪。)挪出屏幕。我们通常将屏幕也就是显示器的屏幕玻璃所处的位置称作Z轴的0.0f点。
 * 所以Z轴上的位移最后定为2.0f。假如您能够看见光源的话,它就浮在您显示器的前方。当然,如果木箱不在显示器的屏幕玻璃后面的话,您也无法看见箱子。
 * 最后一个参数取为1.0f。这将告诉OpenGL这里指定的坐标就是光源的位置,以后的教程中我会多加解释。
 * @return
 */
GLfloat LightPosition[] = {0.0f, 0.0f, 2.0f, 1.0f};                 // 光源位置

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson07::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 Lesson07::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 Lesson07::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 Lesson07::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);                      // 启用光源
    return TRUE;                                // 初始化 OK
}

/**
 * 下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。
 * 如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。
 * @return
 */
int Lesson07::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
}
复制代码

混合

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson08::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 Lesson08::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/Glass.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 Lesson08::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 Lesson08::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);                            // 启用一号光源
    if (light) {
        glEnable(GL_LIGHTING);                      // 启用光源
    } else {
        glDisable(GL_LIGHTING);
    }

    /**
     * 第一行以全亮度绘制此物体,并对其进行50%的alpha混合(半透明)。当混合选项打开时,此物体将会产生50%的透明效果。第二行设置所采用的混合类型。
     * Rui Martins 的补充: alpha通道的值为 0.0意味着物体材质是完全透明的。1.0 则意味着完全不透明。
     */
    glColor4f(1.0f, 1.0f, 1.0f, 0.5f);            // 全亮度, 50% Alpha 混合
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);        // 基于源象素alpha通道值的半透明混合函数

    if (blend)                // 混合打开了么?
    {
        glEnable(GL_BLEND);        // 打开混合
        glDisable(GL_DEPTH_TEST);    // 关闭深度测试
    } else                    // 否则
    {
        glDisable(GL_BLEND);        // 关闭混合
        glEnable(GL_DEPTH_TEST);    // 打开深度测试
    }
    return TRUE;                                // 初始化 OK
}

/**
 * 下一段包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出现。以后的每个教程中我都会在例程的此处增加新的代码。
 * 如果您对OpenGL已经有所了解的话,您可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。
 * @return
 */
int Lesson08::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
}

但是怎样才能在使用纹理贴图的时候指定混合时的颜色呢?很简单,在调整贴图模式时,文理贴图的每个象素点的颜色都是由alpha通道参数与当前地象素颜色相乘所得到的。比如,绘制的颜色是 (0.5, 0.6, 0.4),我们会把颜色相乘得到(0.5, 0.6, 0.4, 0.2) (alpha参数在没有指定时,缺省为零)。
就是如此!OpenGL实现Alpha混合的确很简单!

原文注 (11/13/99)
我(NeHe)混色代码进行了修改,以使显示的物体看起来更逼真。同时对源象素和目的象素使用alpha参数来混合,会导致物体的人造痕迹看起来很明显。 会使得物体的背面沿着侧面的地方显得更暗。基本上物体会看起来很怪异。我所用的混色方法也许不是最好的,但的确能够工作。启用光源之后,物体看起来很逼真。感谢Tom提供的原始代码,他采用的混色方法是正确的,但物体看起来并不象所期望的那样吸引人:)
代码所作的再次修改是因为在某些显卡上glDepthMask()函数存在寻址问题。这条命令在某些卡上启用或关闭深度缓冲测试时似乎不是很有效,所以我已经将启用或关闭深度缓冲测试的代码转成老式的glEnable和glDisable。

纹理贴图的Alpha混合
用于纹理贴图的alpha参数可以象颜色一样从问题贴图中读取。方法如下,您需要在载入所需的材质同时取得其的alpha参数。然后在调用glTexImage2D()时使用GL_RGBA的颜色格式。
复制代码

3D空间中移动图像

如何加载一个灰阶位图纹理,(使用混色)去掉它的背景色后,再给它上色,最后让它在3D场景中移动。我已经向您展示了如何创建漂亮的颜色与动画效果。实现原理是在原始位图上再重叠一份位图拷贝。

#define TRUE 1
#define FALSE 0

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson09::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 Lesson09::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/Star.bmp", TextureImage)) {
        Status = TRUE;                            // 将 Status 设为 TRUE

        glGenTextures(1, &texture[1]);              // 创建一个纹理

        // 创建一个线性滤波纹理
        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, TextureImage->sizeX, TextureImage->sizeY, 0, 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 Lesson09::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纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 *
 * 现在设置OpenGL的渲染方式。这里不打算使用深度测试,如果您使用第一课的代码的话,请确认是否已经去掉了 glDepthFunc(GL_LEQUAL); 和 glEnable(GL_DEPTH_TEST);两行。
 * 否则,您所见到的效果将会一团糟。这里我们使用了纹理映射,因此请您确认您已经加上了这些第一课中所没有的代码。您会注意到我们通过混色来启用了纹理映射。
 * @return
 */
int Lesson09::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);                            // 设置深度缓存
    /**
     * 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
     */
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // 告诉系统对透视进行修正

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);        // 基于源象素alpha通道值的半透明混合函数
    glEnable(GL_BLEND);

    /**
     * 以下是新增的代码。设置了每颗星星的起始角度、距离、和颜色。您会注意到修改结构的属性有多容易。全部50颗星星都会被循环设置。要改变star[1]的角度我们所要做的只是star[1].angle={某个数值};就这么简单!
     */
    for (loop = 0; loop < num; loop++)                // 创建循环设置全部星星
    {
        star[loop].angle = 0.0f;                // 所有星星都从零角度开始
        /**
         * 第loop颗星星离中心的距离是将loop的值除以星星的总颗数,然后乘上5.0f。基本上这样使得后一颗星星比前一颗星星离中心更远一点。
         * 这样当loop为50时(最后一颗星星),loop 除以 num正好是1.0f。之所以要乘以5.0f是因为1.0f*5.0f 就是 5.0f。
         * 5.0f已经很接近屏幕边缘。我不想星星飞出屏幕,5.0f是最好的选择了。当然如果如果您将场景设置的更深入屏幕里面的话,也许可以使用大于5.0f的数值,但星星看起来就更小一些(都是透视的缘故)。
         * 您还会注意到每颗星星的颜色都是从0~255之间的一个随机数。也许您会奇怪为何这里的颜色得取值范围不是OpenGL通常的0.0f~1.0f之间。
         * 这里我们使用的颜色设置函数是glColor4ub,而不是以前的glColor4f。ub意味着参数是Unsigned Byte型的。一个byte的取值范围是0~255。这里使用byte值取随机整数似乎要比取一个浮点的随机数更容易一些。
         */
        star[loop].dist = (float(loop) / num) * 5.0f;        // 计算星星离中心的距离
        star[loop].r = rand() % 256;            // 为star[loop]设置随机红色分量
        star[loop].g = rand() % 256;            // 为star[loop]设置随机红色分量
        star[loop].b = rand() % 256;            // 为star[loop]设置随机红色分量
    }
    return TRUE;                                // 初始化 OK
}

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

    for (loop = 0; loop < num; loop++)                // 循环设置所有的星星
    {
        glLoadIdentity();                // 绘制每颗星星之前,重置模型观察矩阵
        glTranslatef(0.0f, 0.0f, zoom);            // 深入屏幕里面
        glRotatef(tilt, 1.0f, 0.0f, 0.0f);            // 倾斜视角
        /**
         * 现在我们来移动星星。星星开始时位于屏幕的中心。我们要做的第一件事是把场景沿Y轴旋转。如果我们旋转90度的话,X轴不再是自左至右的了,他将由里向外穿出屏幕。
         * 为了让大家更清楚些,举个例子。假想您站在房子中间。再设想您左侧的墙上写着-x,前面的墙上写着-z,右面墙上就是+x咯,您身后的墙上则是+z。
         * 加入整个房子向右转90度,但您没有动,那么前面的墙上将是-x而不再是-z了。所有其他的墙也都跟着移动。-z出现在右侧,+z出现在左侧,+x出现在您背后。神经错乱了吧?
         * 通过旋转场景,我们改变了x和z平面的方向。第二行代码沿x轴移动一个正值。通常x轴上的正值代表移向了屏幕的右侧(也就是通常的x轴的正向),但这里由于我们绕y轴旋转了坐标系,x轴的正向可以是任意方向。
         * 如果我们转180度的话,屏幕的左右侧就镜像反向了。因此,当我们沿 x轴正向移动时,可能向左,向右,向前或向后。
         */
        glRotatef(star[loop].angle, 0.0f, 1.0f, 0.0f);    // 旋转至当前所画星星的角度
        glTranslatef(star[loop].dist, 0.0f, 0.0f);    // 沿X轴正向移动
        /**
         * 接着的代码带点小技巧。星星实际上是一个平面的纹理。现在您在屏幕中心画了个平面的四边形然后贴上纹理,这看起来很不错。一切都如您所想的那样。
         * 但是当您当您沿着y轴转上个90度的话,纹理在屏幕上就只剩右侧和左侧的两条边朝着您。看起来就是一条细线。这不是我们所想要的。
         * 我们希望星星永远正面朝着我们,而不管屏幕如何旋转或倾斜。我们通过在绘制星星之前,抵消对星星所作的任何旋转来实现这个愿望。您可以采用逆序来抵消旋转。
         * 当我们倾斜屏幕时,我们实际上以当前角度旋转了星星。通过逆序,我们又以当前角度"反旋转"星星。也就是以当前角度的负值来旋转星星。
         * 就是说,如果我们将星星旋转了10度的话,又将其旋转-10度来使星星在那个轴上重新面对屏幕。下面的第一行抵消了沿y轴的旋转。然后,我们还需要抵消掉沿x轴的屏幕倾斜。
         * 要做到这一点,我们只需要将屏幕再旋转-tilt倾角。在抵消掉x和y轴的旋转后,星星又完全面对着我们了。
         */
        glRotatef(-star[loop].angle, 0.0f, 1.0f, 0.0f);    // 取消当前星星的角度
        glRotatef(-tilt, 1.0f, 0.0f, 0.0f);        // 取消屏幕倾斜
        /**
         * 如果 twinkle 为 TRUE,我们在屏幕上先画一次不旋转的星星:将星星总数(num) 减去当前的星星数(loop)再减去1,来提取每颗星星的不同颜色(这么做是因为循环范围从0到num-1)。
         * 举例来说,结果为10的时候,我们就使用10号星星的颜色。这样相邻星星的颜色总是不同的。这不是个好法子,但很有效。最后一个值是alpha通道分量。
         * 这个值越小,这颗星星就越暗。由于启用了twinkle,每颗星星最后会被绘制两遍。程序运行起来会慢一些,这要看您的机器性能如何了。但两遍绘制的星星颜色相互融合,会产生很棒的效果。
         * 同时由于第一遍的星星没有旋转,启用twinkle后的星星看起来有一种动画效果。(如果您这里看不懂得话,就自己去看程序的运行效果吧。)值得注意的是给纹理上色是件很容易的事。
         * 尽管纹理本身是黑白的,纹理将变成我们在绘制它之前选定的任意颜色。此外,同样值得注意的是我们在这里使用的颜色值是byte型的,而不是通常的浮点数。甚至alpha通道分量也是如此。
         */
        if (twinkle)                    // 启用闪烁效果
        {
            // 使用byte型数值指定一个颜色
            glColor4ub(star[(num - loop) - 1].r, star[(num - loop) - 1].g, star[(num - loop) - 1].b, 255);
            glBegin(GL_QUADS);            // 开始绘制纹理映射过的四边形
            glTexCoord2f(0.0f, 0.0f);
            glVertex3f(-1.0f, -1.0f, 0.0f);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3f(1.0f, -1.0f, 0.0f);
            glTexCoord2f(1.0f, 1.0f);
            glVertex3f(1.0f, 1.0f, 0.0f);
            glTexCoord2f(0.0f, 1.0f);
            glVertex3f(-1.0f, 1.0f, 0.0f);
            glEnd();                // 四边形绘制结束
        }
        /**
         * 现在绘制第二遍的星星。唯一和前面的代码不同的是这一遍的星星肯定会被绘制,并且这次的星星绕着z轴旋转。
         */
        glRotatef(spin, 0.0f, 0.0f, 1.0f);            // 绕z轴旋转星星
        // 使用byte型数值指定一个颜色
        glColor4ub(star[loop].r, star[loop].g, star[loop].b, 255);
        glBegin(GL_QUADS);                // 开始绘制纹理映射过的四边形
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 0.0f);
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 0.0f);
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 0.0f);
        glEnd();                    // 四边形绘制结束
        /**
         * 以下的代码代表星星的运动。我们增加spin的值来旋转所有的星星(公转)。然后,将每颗星星的自转角度增加loop/num。这使离中心更远的星星转的更快。
         * 最后减少每颗星星离屏幕中心的距离。这样看起来,星星们好像被不断地吸入屏幕的中心。
         */
        spin += 0.01f;                    // 星星的公转
        star[loop].angle += float(loop) / num;        // 改变星星的自转角度
        star[loop].dist -= 0.01f;                // 改变星星离中心的距离
        /**
         * 接着几行检查星星是否已经碰到了屏幕中心。当星星碰到屏幕中心时,我们为它赋一个新颜色,然后往外移5个单位,这颗星星将踏上它回归屏幕中心的旅程。
         */
        if (star[loop].dist < 0.0f)            // 星星到达中心了么
        {
            star[loop].dist += 5.0f;            // 往外移5个单位
            star[loop].r = rand() % 256;        // 赋一个新红色分量
            star[loop].g = rand() % 256;        // 赋一个新绿色分量
            star[loop].b = rand() % 256;        // 赋一个新蓝色分量
        }
    }
    return TRUE;                                //  一切 OK
}
复制代码

加载3D世界

#define TRUE 1
#define FALSE 0

// 定义Bmp的结构体
struct _AUX_RGBImageRec {
    unsigned long sizeX;        // X方向大小
    unsigned long sizeY;        // y方向大小
    unsigned char *data;        // 数据地址
};

typedef struct _AUX_RGBImageRec AUX_RGBImageRec;

/**
 * 三角形本质上是由一些(两个以上)顶点组成的多边形,顶点同时也是我们的最基本的分类单位。顶点包含了OpenGL真正感兴趣的数据。我们用3D空间中的坐标值(x,y,z)以及它们的纹理坐标(u,v)来定义三角形的每个顶点。
 */
typedef struct tagVERTEX						// 创建Vertex顶点结构
{
    float x, y, z;							// 3D 坐标
    float u, v;							// 纹理坐标
} VERTEX;								// 命名为VERTEX

/**
 * 一个sector(区段)包含了一系列的多边形,所以下一个目标就是triangle(我们将只用三角形,这样写代码更容易些)。
 */
typedef struct tagTRIANGLE						// 创建Triangle三角形结构
{
    VERTEX vertex[3];						// VERTEX矢量数组,大小为3
} TRIANGLE;								// 命名为 TRIANGLE

/**
 * 当您想要使用一系列的数字来完美的表达3D环境时,随着环境复杂度的上升,这个工作的难度也会随之上升。出于这个原因,我们必须将数据归类,使其具有更多的可操作性风格。
 * 在程序清单头部出现了sector(区段)的定义。每个3D世界基本上可以看作是sector(区段)的集合。一个sector(区段)可以是一个房间、一个立方体、或者任意一个闭合的区间。
 */
typedef struct tagSECTOR						// 创建Sector区段结构
{
    int numtriangles;						// Sector中的三角形个数
    TRIANGLE* triangle;						// 指向三角数组的指针
} SECTOR;								// 命名为SECTOR

void readstr(FILE *f,char *string)
{
    do
    {
        fgets(string, 255, f);
    } while ((string[0] == '/') || (string[0] == '\n'));
    return;
}

/**
 * 载入文件
 * 在程序内部直接存储数据会让程序显得太过死板和无趣。从磁盘上载入世界资料,会给我们带来更多的弹性,可以让我们体验不同的世界,而不用被迫重新编译程序。
 * 另一个好处就是用户可以切换世界资料并修改它们而无需知道程序如何读入输出这些资料的。数据文件的类型我们准备使用文本格式。这样编辑起来更容易,写的代码也更少。
 * 等将来我们也许会使用二进制文件。问题是,怎样才能从文件中取得数据资料呢?首先,创建一个叫做SetupWorld()的新函数。把这个文件定义为filein,并且使用只读方式打开文件。
 * 我们必须在使用完毕之后关闭文件。
 */
void Lesson10::SetupWorld(char *worldfile) {
    float x, y, z, u, v;
    int numtriangles;
    FILE *filein;
    char oneline[255];
    filein = fopen(worldfile, "rt");				// File To Load World Data From

    readstr(filein,oneline);
    sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);

    sector1.triangle = new TRIANGLE[numtriangles];
    sector1.numtriangles = numtriangles;
    for (int loop = 0; loop < numtriangles; loop++)
    {
        for (int vert = 0; vert < 3; vert++)
        {
            readstr(filein,oneline);
            // 数据文件中每个三角形都以如下形式声明:
            // X1 Y1 Z1 U1 V1
            // X2 Y2 Z2 U2 V2
            // X3 Y3 Z3 U3 V3
            sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
            sector1.triangle[loop].vertex[vert].x = x;
            sector1.triangle[loop].vertex[vert].y = y;
            sector1.triangle[loop].vertex[vert].z = z;
            sector1.triangle[loop].vertex[vert].u = u;
            sector1.triangle[loop].vertex[vert].v = v;
        }
    }
    fclose(filein);
    return;                                // 返回
}

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson10::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 Lesson10::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/Mud.bmp", TextureImage)) {
        Status = TRUE;                            // 将 Status 设为 TRUE

        glGenTextures(1, &texture[1]);              // 创建一个纹理

        // 创建一个线性滤波纹理
        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, TextureImage->sizeX, TextureImage->sizeY, 0, 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 Lesson10::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纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 *
 * 现在设置OpenGL的渲染方式。这里不打算使用深度测试,如果您使用第一课的代码的话,请确认是否已经去掉了 glDepthFunc(GL_LEQUAL); 和 glEnable(GL_DEPTH_TEST);两行。
 * 否则,您所见到的效果将会一团糟。这里我们使用了纹理映射,因此请您确认您已经加上了这些第一课中所没有的代码。您会注意到我们通过混色来启用了纹理映射。
 * @return
 */
int Lesson10::InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())                            // 调用纹理载入子例程
    {
        return FALSE;                            // 如果未能载入,返回FALSE
    }
    glEnable(GL_TEXTURE_2D);                        // 启用纹理映射
    /**
     * 下一行设置清除屏幕时所用的颜色。如果您对色彩的工作原理不清楚的话,我快速解释一下。
     * 色彩值的范围从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.0f);                    // 黑色背景
    /**
     * 接下来的三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
     * 我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。
     * 这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。
     */
    glClearDepth(1.0f);                            // 设置深度缓存
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);
    /**
     * 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
     */
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // 告诉系统对透视进行修正

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);        // 基于源象素alpha通道值的半透明混合函数

    SetupWorld("/home/zpw/Documents/CLionProjects/opengl_study/Data/World.txt");

    return TRUE;                                // 初始化 OK
}

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


    GLfloat x_m, y_m, z_m, u_m, v_m;				// 顶点的临时 X, Y, Z, U 和 V 的数值
    GLfloat xtrans = -xpos;						// 用于游戏者沿X轴平移时的大小
    GLfloat ztrans = -zpos;						// 用于游戏者沿Z轴平移时的大小
    GLfloat ytrans = -walkbias-0.25f;				// 用于头部的上下摆动
    GLfloat sceneroty = 360.0f - yrot;				// 位于游戏者方向的360度角

    int numtriangles;						// 保有三角形数量的整数

    glRotatef(lookupdown,1.0f,0,0);					// 上下旋转
    glRotatef(sceneroty,0,1.0f,0);					// 根据游戏者正面所对方向所作的旋转

    glTranslatef(xtrans, ytrans, ztrans);				// 以游戏者为中心的平移场景
    glBindTexture(GL_TEXTURE_2D, texture[filter]);			// 根据 filter 选择的纹理

    numtriangles = sector1.numtriangles;				// 取得Sector1的三角形数量

    // 逐个处理三角形
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)		// 遍历所有的三角形
    {
        glBegin(GL_TRIANGLES);					// 开始绘制三角形
        glNormal3f( 0.0f, 0.0f, 1.0f);			// 指向前面的法线
        x_m = sector1.triangle[loop_m].vertex[0].x;	// 第一点的 X 分量
        y_m = sector1.triangle[loop_m].vertex[0].y;	// 第一点的 Y 分量
        z_m = sector1.triangle[loop_m].vertex[0].z;	// 第一点的 Z 分量
        u_m = sector1.triangle[loop_m].vertex[0].u;	// 第一点的 U  纹理坐标
        v_m = sector1.triangle[loop_m].vertex[0].v;	// 第一点的 V  纹理坐标
        glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 设置纹理坐标和顶点

        x_m = sector1.triangle[loop_m].vertex[1].x;	// 第二点的 X 分量
        y_m = sector1.triangle[loop_m].vertex[1].y;	// 第二点的 Y 分量
        z_m = sector1.triangle[loop_m].vertex[1].z;	// 第二点的 Z 分量
        u_m = sector1.triangle[loop_m].vertex[1].u;	// 第二点的 U  纹理坐标
        v_m = sector1.triangle[loop_m].vertex[1].v;	// 第二点的 V  纹理坐标
        glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 设置纹理坐标和顶点

        x_m = sector1.triangle[loop_m].vertex[2].x;	// 第三点的 X 分量
        y_m = sector1.triangle[loop_m].vertex[2].y;	// 第三点的 Y 分量
        z_m = sector1.triangle[loop_m].vertex[2].z;	// 第三点的 Z 分量
        u_m = sector1.triangle[loop_m].vertex[2].u;	// 第二点的 U  纹理坐标
        v_m = sector1.triangle[loop_m].vertex[2].v;	// 第二点的 V  纹理坐标
        glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 设置纹理坐标和顶点
        glEnd();						// 三角形绘制结束
    }
    return TRUE;                                //  一切 OK
}
复制代码

飘动的旗帜

#define TRUE 1
#define FALSE 0

/*
** 利用freeimage加载bmp图像
** 此函数在Linux系统上可以作为常用util调用
*/
GLboolean Lesson11::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 Lesson11::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/Tim.bmp", TextureImage)) {
        Status = TRUE;                            // 将 Status 设为 TRUE

        glGenTextures(1, &texture[1]);              // 创建一个纹理

        // 创建一个线性滤波纹理
        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, TextureImage->sizeX, TextureImage->sizeY, 0, 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 Lesson11::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纹理映射。
 * 如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。
 *
 * 现在设置OpenGL的渲染方式。这里不打算使用深度测试,如果您使用第一课的代码的话,请确认是否已经去掉了 glDepthFunc(GL_LEQUAL); 和 glEnable(GL_DEPTH_TEST);两行。
 * 否则,您所见到的效果将会一团糟。这里我们使用了纹理映射,因此请您确认您已经加上了这些第一课中所没有的代码。您会注意到我们通过混色来启用了纹理映射。
 * @return
 */
int Lesson11::InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
    if (!LoadGLTextures())                                // Jump To Texture Loading Routine ( NEW )
    {
        return FALSE;                                    // If Texture Didn't Load Return FALSE
    }

    glEnable(GL_TEXTURE_2D);                            // Enable Texture Mapping ( NEW )
    glShadeModel(GL_SMOOTH);                            // Enable Smooth Shading
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                // Black Background
    glClearDepth(1.0f);                                    // Depth Buffer Setup
    glEnable(GL_DEPTH_TEST);                            // Enables Depth Testing
    glDepthFunc(GL_LEQUAL);                                // The Type Of Depth Testing To Do
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // Really Nice Perspective Calculations
    glPolygonMode(GL_BACK, GL_FILL);                    // Back Face Is Solid
    glPolygonMode(GL_FRONT, GL_LINE);                    // Front Face Is Made Of Lines

    for (int x = 0; x < 45; x++) {
        for (int y = 0; y < 45; y++) {
            points[x][y][0] = float((x / 5.0f) - 4.5f);
            points[x][y][1] = float((y / 5.0f) - 4.5f);
            points[x][y][2] = float(sin((((x / 5.0f) * 40.0f) / 360.0f) * 3.141592654 * 2.0f));
        }
    }

    return TRUE;
}

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

    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,-12.0f);

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

    glBindTexture(GL_TEXTURE_2D, texture[0]);

    glBegin(GL_QUADS);
    for( x = 0; x < 44; x++ )
    {
        for( y = 0; y < 44; y++ )
        {
            float_x = float(x)/44.0f;
            float_y = float(y)/44.0f;
            float_xb = float(x+1)/44.0f;
            float_yb = float(y+1)/44.0f;

            glTexCoord2f( float_x, float_y);
            glVertex3f( points[x][y][0], points[x][y][1], points[x][y][2] );

            glTexCoord2f( float_x, float_yb );
            glVertex3f( points[x][y+1][0], points[x][y+1][1], points[x][y+1][2] );

            glTexCoord2f( float_xb, float_yb );
            glVertex3f( points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2] );

            glTexCoord2f( float_xb, float_y );
            glVertex3f( points[x+1][y][0], points[x+1][y][1], points[x+1][y][2] );
        }
    }
    glEnd();

    if( wiggle_count == 2 )
    {
        for( y = 0; y < 45; y++ )
        {
            hold=points[0][y][2];
            for( x = 0; x < 44; x++)
            {
                points[x][y][2] = points[x+1][y][2];
            }
            points[44][y][2]=hold;
        }
        wiggle_count = 0;
    }

    wiggle_count++;

    xrot+=0.3f;
    yrot+=0.2f;
    zrot+=0.4f;

    return TRUE;										// Keep Going
}
复制代码
关注下面的标签,发现更多相似文章
评论