第六课:纹理映射
纹理映射:
在这一课里,我将教会你如何把纹理映射到立方体的六个面。
学习 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;// 继续运行
}
原文及其个版本源代码下载:
http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=06
- 大小: 3.7 KB
分享到:
相关推荐
6. 导数的综合运用:第18题和第22题分别要求求解多个函数的导数,并用导数来解决问题,这需要考生熟悉各种导数公式和运算法则。 7. 切线方程的求解:第20题和第21题均涉及求曲线的切线方程,这需要考生能够根据导数...
6. **概率问题**:第六题是关于概率的计算,立方体的骰子投掷,要求计算朝上的数字是朝下数字的两倍的概率。 7. **方程与几何**:第七题是解方程2x - 9x + 18 = 0,并将其与等腰三角形的底和腰联系起来,求解三角形...
填空题部分则需要学生准确计算和推理,如第十三题要求写出命题的否定,第十四题可能涉及指数运算,第十五题和第十六题分别需要找出函数的单调区间和使得函数不单调的参数k的取值范围。 解答题部分则更加深入,包括...
【标题】和【描述】提到的是讷河市实验学校初三语文月考试卷及答案的文档,这是一份针对初中生的语文考试资料。【标签】表明这是一个文档类的内容。 【部分内容】展示了试卷的部分题目,包括语言理解、漫画解读、...
黑龙江省讷河市拉哈一中2020-2021学年高一下学期4月月考语文试卷 Word版含答案.doc
6. 函数值域:题目6求解函数的值域,需要理解函数的定义域和图像特征。 7. 逻辑推理:题目7是一个逻辑推理题,涉及到三个陈述的真假判断,通过排除法确定唯一正确的结果。 8. 数列求和:题目8是一个等比数列的问题...
公路桥梁隧道施工组织设计是大型土木工程项目中的关键环节,对于鸡西至讷河公路建设项目C23标段这样的工程,其施工组织设计涉及到多个重要知识点。以下将详细阐述这些内容: 1. **项目背景与目标**:鸡西至讷河公路...
《鸡西至讷河公路建设项目C23标段施工组织设计》是一份全面阐述公路建设过程中施工规划与管理的重要文档,旨在确保工程的顺利进行、质量和安全。在公路桥梁隧道施工组织设计中,涵盖了许多关键知识点,下面将逐一...
第六题要求找到一条切线的方程,通过解方程找出切点坐标。第七题关注的是函数在某点的切线倾斜角,这需要计算导数值并将其转换为角度。第八题是对导数性质的理解,判断导数运算的正确性。第九题要求直接应用导数求解...
- **考试内容**:根据标题“黑龙江省齐齐哈尔市讷河一中2020届高三数学联考试题文PDF”,可以推测此次联考主要涉及高三阶段的数学知识点,包括但不限于函数、数列、不等式、立体几何、解析几何、概率统计等内容。...
- **考试范围**:根据标题“黑龙江省齐齐哈尔市讷河一中2020届高三数学联考试题理PDF”,可以推测此次联考主要面向高三理科学生,考试内容将涵盖高中数学的所有核心知识点。 ### 高三数学核心知识点 从给定的部分...
此资源为“黑龙江省齐齐哈尔市市讷河一中2020届高三英语联考试题PDF”,是一份针对高三学生的英语考试试卷,主要目的是检验学生在高三阶段对英语知识的掌握程度,为高考做好准备。试题包含了听力、阅读、写作等多个...
黑龙江省齐齐哈尔市市讷河一中2020届高三理综联考试题PDF
《英雄》音乐微案例;黑龙江省讷河市六合镇中心学校赵明丽.doc
保护青山绿水 构筑生态文明——讷河市新能源公交车全面上路运营掠影.pdf
2021届黑龙江省讷河市拉哈一中高一下学期历史3月月考试题.doc
#### 六、教学过程详解 ##### 第一课时 1. **导入环节**:教师通过展示各种农具的图片,引发学生的兴趣。随后组织学生分组讨论,让他们分享自己对这些农具的认识。 - **知识点**:介绍几种典型的农具(如锄头、...
黑龙江省讷河市张静中学第一学期初三期中考试语文考试题及答案.doc.pdf
** 结构:第六题 "Why don’t you join an English club to practice speaking English?","Why don't you + 动词原形?" 是提出建议的表达方式,意为“你为什么不……呢?”后接动词原形。 6. **现在完成时与现在...