纹理映射:

在这一课里,我将教会你如何把纹理映射到立方体的六个面。

学习 texture map 纹理映射(贴图)有很多好处。比方说您想让一颗导弹飞过屏幕。根据前几课的知识,我们最可行的办法可能是很多个多边形来构建导弹的轮廓并加上有趣的颜色。使用纹理映射,您可以使用真实的导弹图像并让它飞过屏幕。您觉得哪个更好看?照片还是一大堆三角形和四边形?使用纹理映射的好处还不止是更好看,而且您的程序运行会更快。导弹贴图可能只是一个飞过窗口的四边形。一个由多边形构建而来的导弹却很可能包括成百上千的多边形。很显然,贴图极大的节省了CPU时间。
现在我们在第一课的代码开始处增加五行新代码。新增的第一行是 #include <stdio.h> 。它允许我们对文件进行操作,为了在后面的代码中使用 fopen() ,我们增加了这一行。然后我们增加了三个新的浮点变量... xrot , yrot 和 zrot 。这些变量用来使立方体绕X、Y、Z轴旋转。最后一行 GLuint texture[1] 为一个纹理分配存储空间。如果您需要不止一个的纹理,应该将参数1改成您所需要的参数。

#include    <stdio.h>                            // 标准输入/输出库的头文件
#include <glaux.h> // GLaux库的头文件 GLfloat xrot; // X 旋转量
GLfloat yrot; // Y 旋转量
GLfloat zrot; // Z 旋转量

GLuint texture[1]; // 存储一个纹理

紧跟上面的代码在 ReSizeGLScene() 之前,我们增加了下面这一段代码。这段代码用来加载位图文件。如果文件不存在,返回 NULL 告知程序无法加载位图。在我开始解释这段代码之前,关于用作纹理的图像我想有几点十分重要,并且您必须明白。此图像的宽和高必须是2的n次方;宽度和高度最小必须是64象素;并且出于兼容性的原因,图像的宽度和高度不应超过256象素。如果您的原始素材的宽度和高度不是64,128,256象素的话,使用图像处理软件重新改变图像的大小。可以肯定有办法能绕过这些限制,但现在我们只需要用标准的纹理尺寸。

首先,我们创建一个文件句柄。句柄是个用来鉴别资源的数值,它使程序能够访问此资源。我们开始先将句柄设为 NULL 。

AUX_RGBImageRec *LoadBMP(char *Filename)                    // 载入位图图象
{
FILE *File=NULL; // 文件句柄

接下来检查文件名是否已提供。因为 LoadBMP() 可以无参数调用,所以我们不得不检查一下。您可不想什么都没载入吧.....:)

    if (!Filename)                                // 确保文件名已提供
{
return NULL; // 如果没提供,返回 NULL
}

接着检查文件是否存在。下面这一行尝试打开文件。

    File=fopen(Filename,"r");                        // 尝试打开文件

如果我们能打开文件的话,很显然文件是存在的。使用 fclose(File) 关闭文件。 auxDIBImageLoad(Filename) 读取图象数据并将其返回。

    if (File)                                // 文件存在么?
{
fclose(File); // 关闭句柄
return auxDIBImageLoad(Filename); // 载入位图并返回指针
}

如果我们不能打开文件,我们将返回NULL。这意味着文件无法载入。程序在后面将检查文件是否已载入。如果没有,我们将退出程序并弹出错误消息。

    return NULL;                                // 如果载入失败,返回 NULL
}

下一部分代码载入位图(调用上面的代码)并转换成纹理。

int LoadGLTextures()                                // 载入位图(调用上面的代码)并转换成纹理
{

然后设置一个叫做 Status 的变量。我们使用它来跟踪是否能够载入位图以及能否创建纹理。 Status 缺省设为 FALSE (表示没有载入或创建任何东东)。

    int Status=FALSE;                            // 状态指示器

现在我们创建存储位图的图像记录。次记录包含位图的宽度、高度和数据。

    AUX_RGBImageRec *TextureImage[1];                    // 创建纹理的存储空间

清除图像记录,确保其内容为空

    memset(TextureImage,0,sizeof(void *)*1);                // 将指针设为 NULL

现在载入位图,并将其转换为纹理。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 调用 LoadBMP() 的代码。载入 Data 目录下的 NeHe.bmp 位图文件。如果一切正常,图像数据将存放在 TextureImage[0] 中, Status 被设为 TRUE ,然后我们开始创建纹理。

    // 载入位图,检查有无错误,如果位图没找到则退出
if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))
{
Status=TRUE; // 将 Status 设为 TRUE

现在使用中 TextureImage[0] 的数据创建纹理。第一行 glGenTextures(1, &texture[0]) 告诉OpenGL我们想生成一个纹理名字(如果您想载入多个纹理,加大数字)。值得注意的是,开始我们使用 GLuint texture[1] 来创建一个纹理的存储空间,您也许会认为第一个纹理就是存放在 &texture[1] 中的,但这是错的。正确的地址应该是 &texture[0] 。同样如果使用 GLuint texture[2] 的话,第二个纹理存放在 texture[1] 中。『 译者注:学C的,在这里应该没有障碍,数组就是从零开始的嘛。』

第二行 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[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->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[0])                            // 纹理是否存在
{
if (TextureImage[0]->data) // 纹理图像是否存在
{
free(TextureImage[0]->data); // 释放纹理图像占用的内存
}
free(TextureImage[0]); // 释放图像结构
}

最后返回状态变量。如果一切OK,变量 Status 的值为 TRUE 。否则为 FALSE 。

    return Status;                                // 返回 Status
}

我只在 InitGL 中增加很少的几行代码。但为了方便您查看增加了哪几行,我这段代码全部重贴一遍。 if (!LoadGLTextures()) 这行代码调用上面讲的子例程载入位图并生成纹理。如果因为任何原因 LoadGLTextures() 调用失败,接着的一行返回FALSE。如果一切OK,并且纹理创建好了,我们启用2D纹理映射。如果您忘记启用的话,您的对象看起来永远都是纯白色,这一定不是什么好事。

int InitGL(GLvoid)                                // 此处开始对OpenGL进行所有设置
{
if (!LoadGLTextures()) // 调用纹理载入子例程
{
return FALSE; // 如果未能载入,返回FALSE
}
glEnable(GL_TEXTURE_2D); // 启用纹理映射 glShadeModel(GL_SMOOTH); // 启用阴影*滑 glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // 黑色背景 glClearDepth(1.0f); // 设置深度缓存 glEnable(GL_DEPTH_TEST); // 启用深度测试 glDepthFunc(GL_LEQUAL); // 所作深度测试的类型 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 真正精细的透视修正 return TRUE; // 初始化 OK
}

现在我们绘制贴图『译者注:其实贴图就是纹理映射。将术语换来换去不好,我想少打俩字。^_^』过的立方体。这段代码被狂注释了一把,应该很好懂。开始两行代码 glClear() 和 glLoadIdentity() 是第一课中就有的代码。 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 清除屏幕并设为我们在 InitGL() 中选定的颜色,本例中是黑色。深度缓存也被清除。模型观察矩阵也使用glLoadIdentity()重置。

int DrawGLScene(GLvoid)                                // 从这里开始进行所有的绘制
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存 glLoadIdentity(); // 重置当前的模型观察矩阵 glTranslatef(0.0f,0.0f,-5.0f); // 移入屏幕 5 个单位

下面三行使立方体绕X、Y、Z轴旋转。旋转多少依赖于变量 xrot , yrot 和 zrot 的值。

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

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

    glRotatef(zrot,0.0f,0.0f,1.0f);                        // 绕Z轴旋转

下一行代码选择我们使用的纹理。如果您在您的场景中使用多个纹理,您应该使用来 glBindTexture(GL_TEXTURE_2D, texture[ 所使用纹理对应的数字 ]) 选择要绑定的纹理。当您想改变纹理时,应该绑定新的纹理。有一点值得指出的是,您不能在 glBegin() 和 glEnd() 之间绑定纹理,必须在 glBegin() 之前或 glEnd() 之后绑定。注意我们在后面是如何使用 glBindTexture 来指定和绑定纹理的。

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

为了将纹理正确的映射到四边形上,您必须将纹理的右上角映射到四边形的右上角,纹理的左上角映射到四边形的左上角,纹理的右下角映射到四边形的右下角,纹理的左下角映射到四边形的左下角。如果映射错误的话,图像显示时可能上下颠倒,侧向一边或者什么都不是。

glTexCoord2f 的第一个参数是X坐标。 0.0f 是纹理的左侧。 0.5f 是纹理的中点, 1.0f 是纹理的右侧。 glTexCoord2f 的第二个参数是Y坐标。 0.0f 是纹理的底部。 0.5f 是纹理的中点, 1.0f 是纹理的顶部。

所以纹理的左上坐标是 X:0.0f,Y:1.0f ,四边形的左上顶点是 X: -1.0f,Y:1.0f 。其余三点依此类推。

试着玩玩 glTexCoord2f 的X,Y坐标参数。把 1.0f 改为 0.5f 将只显示纹理的左半部分,把 0.0f 改为 0.5f 将只显示纹理的右半部分。

    glBegin(GL_QUADS);
// 前面
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(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(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, 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();

现在增加 xrot , yrot 和 zrot 的值。尝试变化每次各变量的改变值来调节立方体的旋转速度,或改变+/-号来调节立方体的旋转方向。

    xrot+=0.3f;                                // X 轴旋转

    yrot+=0.2f;                                // Y 轴旋转

    zrot+=0.4f;                                // Z 轴旋转

    return true;                                // 继续运行
}

现在您应该比较好的理解纹理映射(贴图)了。您应该掌握了给任意四边形表面贴上您所喜爱的图像的技术。一旦您对2D纹理映射的理解感到自信的时候,试试给立方体的六个面贴上不同的纹理。

当您理解纹理坐标的概念后,纹理映射并不难理解。!如果您有什么意见或建议请给我EMAIL。如果您认为有什么不对或可以改进,请告诉我。

第06课 OpenGL 纹理映射的更多相关文章

  1. NeHe OpenGL教程 第六课:纹理映射

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  2. 第11课 OpenGL 飘动的旗帜

    飘动的旗帜: 这一课从第六课的代码开始,创建一个飘动的旗帜.我相信在这课结束的时候,你可以掌握纹理映射和混合操作. 大家好!对那些想知道我在这里作了些什么的朋友,您可以先按文章的末尾所列出的链接,下载 ...

  3. 第09课 OpenGL 移动图像

    3D空间中移动图像: 你想知道如何在3D空间中移动物体,你想知道如何在屏幕上绘制一个图像,而让图像的背景色变为透明,你希望有一个简单的动画.这一课将教会你所有的一切.前面的课程涵盖了基础的OpenGL ...

  4. 第07课 OpenGL 光照和键盘(2)

    下一段代码绘制贴图立方体.我只对新增的代码进行注解.如果您对没有注解的代码有疑问,回头看看第六课. int DrawGLScene(GLvoid) // 从这里开始进行所有的绘制 { glClear( ...

  5. 第07课 OpenGL 光照和键盘(1)

    光照和键盘控制: 在这一课里,我们将添加光照和键盘控制,它让程序看起来更美观. 这一课我会教您如何使用三种不同的纹理滤波方式.教您如何使用键盘来移动场景中的对象,还会教您在OpenGL场景中应用简单的 ...

  6. 第05课 OpenGL 3D空间

    3D空间: 我们使用多边形和四边形创建3D物体,在这一课里,我们把三角形变为立体的金子塔形状,把四边形变为立方体. 在上节课的内容上作些扩展,我们现在开始生成真正的3D对象,而不是象前两节课中那样3D ...

  7. opengl纹理映射总结

    大概步骤: 1.创建纹理对象,并为他指定一个纹理. 2.确定纹理如何应用到每个像素上. 3.启用纹理贴图 4.绘制场景,提供纹理和几何坐标 过滤:由于我们提供的纹理图像很少能和最终的屏幕坐标形成对应, ...

  8. 第06课:GDB 常用命令详解(中)

    本课的核心内容: info 和 thread 命令 next.step.util.finish.return 和 jump 命令 info 和 thread 命令 在前面使用 info break 命 ...

  9. 第12课 OpenGL 显示列表

    显示列表: 想知道如何加速你的OpenGL程序么?这一课将告诉你如何使用OpenGL的显示列表,它通过预编译OpenGL命令来加速你的程序,并可以为你省去很多重复的代码. 这次我将教你如何使用显示列表 ...

随机推荐

  1. PHP中的“重载”是个啥?

    很多面试官在面试的时候都会问一些面向对象的问题,面向对象的三大特性中,多态最主要的实现方式就是方法的重载和重写.但是在PHP中,只有重写,并没有完全的重载能力的实现. 重写,子类重写父类方法. // ...

  2. Shell系列(29)- 单分支if语句格式

    单分支if条件语句 if [ 条件判断 ] ;then 程序 fi 或者 if [ 条件判断 ] then 程序 fi 注意点 if语句使用fi结尾,和一般语言使用大括号结尾不同 [ 条件判断 ]就是 ...

  3. ~/.emacs emacs 配置文件

    windows ~/.emacs (when (>= emacs-major-version 24) (require 'package) (add-to-list 'package-archi ...

  4. Jmeter通过正则表达式提取器提取响应结果数据

    Jmeter进行接口测试常常会运到一个问题:就是第二个请求如何接收上一个请求响应中的参数.比如,现在个学生金币充值的接口,得先调用登录接口然后从返回里面复制一下sign的值,放到cookie里这样才能 ...

  5. EasyExcel无法用转换器或者注解将java字段写入为excel的数值格式

    需求: 在用easyExcel导出报表时,碰到需要将数据转换为数值or货币格式的需求 过程: 1.首先采取转换器的形式 @Override public CellData convertToExcel ...

  6. c++ 的学习 构造函数1

    1. 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作 2.一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象 就是有多个的话    根据参数编译器自行选 ...

  7. 第十二章 Net 5.0 快速开发框架 YC.Boilerplate --千万级数据处理解决方案

    在线文档:http://doc.yc-l.com/#/README 在线演示地址:http://yc.yc-l.com/#/login 源码github:https://github.com/linb ...

  8. RabbitMQ的web页面介绍(三)

    一.Virtual Hosts 每一个 RabbitMQ 服务器都能创建虚拟的消息服务器,我们称之为虚拟主机 (virtual host) ,简称为vhost.每一个 vhost 本质上是一个独立的小 ...

  9. 【原创】C语言和C++常见误区(一)

    本文仅在博客园发布,认准原文地址:https://www.cnblogs.com/jisuanjizhishizatan/p/15414469.html 问题1:int类型占几个字节? 常见误区:占4 ...

  10. 如何查找一个目录中所有c文件的总行数

    如何查找一个目录中所有c文件的行数 面试题问到了一题,如何统计wc文件夹下所有文件的行数,包括了子目录. 最后在 https://blog.csdn.net/a_ran/article/details ...