OpenGL Texture Mapping

学习纹理贴图是十分有用的。 假如我们想使一个导弹飞过屏幕,  用前面的知识我们大概只能用多边形来构造。 而使用纹理映射, 我们完全可以让一个真实的导弹图片飞过屏幕。 一张照片和一个用多边形构造的物体, 你认为哪一个会更好呢?  使用纹理贴图, 不仅能得到更好的视觉效果, 而且还能得到更好的运行效率。 因为使用纹理贴图制作的导弹可以仅仅是一个四边形, 而如果我们完全使用多边形来构造的话, 就有可能需要成百上千的多边形了。 所以使用纹理映射的四边形将为我们节省大量的运算。

 

我们以第一篇教程的代码作为基础, 首先在开始处增加5行新的代码。 第一行新代码是 #include <stdio.h> 因为我们之后要使用fopen() 还有三行新代码是增加了三个浮点变量: xrot yrot zrot 它们分别用于控制立方体在xyz轴上的旋转。 最后一行新代码是 GLuint texture[1] 用于存储一个纹理对象(译注:纹理对象是一个非零的无符号整数) 如果有更多的纹理对象要保存, 就需要修改数组元素的数目为相应的纹理对象的数目。

 

 

#include <windows.h>                                                 // Header File For Windows

#include <stdio.h>                                                // Header File For Standard Input/Output ( NEW )

#include <gl\gl.h>                                                 // Header File For The OpenGL32 Library

#include <gl\glu.h>                                               // Header File For The GLu32 Library

#include <gl\glaux.h>                                           // Header File For The GLaux Library

 

HDC              hDC=NULL;                                             // Private GDI Device Context

HGLRC           hRC=NULL;                                      // Permanent Rendering Context

HWND           hWnd=NULL;                                          // Holds Our Window Handle

HINSTANCE   hInstance;                                       // Holds The Instance Of The Application

 

bool              keys[256];                                              // Array Used For The Keyboard Routine

bool              active=TRUE;                                                 // Window Active Flag

bool              fullscreen=TRUE;                                     // Fullscreen Flag

 

GLfloat          xrot;                                                        // X Rotation ( NEW )

GLfloat          yrot;                                                        // Y Rotation ( NEW )

GLfloat          zrot;                                                        // Z Rotation ( NEW )

 

GLuint           texture[1];                                              // Storage For One Texture ( NEW )

 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);              // Declaration For WndProc

 

 

下面我们增加了一段新的代码, 其作用是读入位图文件。 如果文件不存在的话, 将返回NULL值代表文件不能被读取。 在我解释代码之前, 还要讲一些关于纹理图像的很重要的事情: 纹理图像的宽度和高度必须是2的幂。 宽度或高度最小应是64个像素, 为了兼容性,最多应是256个像素。 如果图像的宽度或高度不是64128256个像素, 就应该重新调整图像尺寸, 这是解决这一限制的方法。 但现在我们只使用标准的纹理尺寸。

(译注: 纹理的最小尺寸限制和最大尺寸限制其实取决于具体的OpenGL实现)

 

 

首先我们创建一个文件句柄, 并初始化为 NULL

 

AUX_RGBImageRec *LoadBMP(char *Filename)                                // Loads A Bitmap Image

{

       FILE *File=NULL;                                            // File Handle

 

 

接下来我们检测文件名是否合法, 因为使用LoadBMP() 的用户有可能根本没有给出文件名。

 

       if (!Filename)                                                  // Make Sure A Filename Was Given

       {

              return NULL;                                           // If Not Return NULL

       }

 

然后, 试着打开文件, 以检测文件是否存在。

 

       File=fopen(Filename,"r");                                      // Check To See If The File Exists

 

 

如果文件能打开证明其一定存在。 我们关闭文件, 然后返回 auxDIBImageLoad(Filename) 读入的图像数据。

 

       if (File)                                                            // Does The File Exist?

       {

              fclose(File);                                             // Close The Handle

              return auxDIBImageLoad(Filename);                           // Load The Bitmap And Return A Pointer

       }

 

 

如果不能打开文件, 我们返回 NULL 之后在程序中我们要检测文件是否读取了, 如果没有的话, 我们将退出程序并给出一个错误消息。

 

       return NULL;                                                  // If Load Failed Return NULL

}

 

 

下面这段代码的作用是读取位图 (通过调用上面的代码) 并转换成纹理。

 

int LoadGLTextures()                                             // Load Bitmaps And Convert To Textures

{

 

 

我们设置一个 Status 变量, 它用于记录我们是否成功地读取了位图并建造了纹理。 我们将其初始化为FLASE (代表什么都没有读取和建造)

 

       int Status=FALSE;                                                 // Status Indicator

 

 

现在我们创建一个用于保存位图的图像记录, 它将持有图像的宽度, 高度和数据。

 

       AUX_RGBImageRec *TextureImage[1];                               // Create Storage Space For The Texture

 

首先清空图像记录。

 

       memset(TextureImage,0,sizeof(void *)*1);                        // Set The Pointer To NULL

 

 

现在我们读入位图。 TextureImage[0]=LoadBMP("Data/NeHe.bmp") 将调用上面的LoadBMP() 读入 Data 目录中的 NeHe.bmp 如果顺利, 图像数据会保存在TextureImage[0] 中, Status 被设置为 TRUE 然后我们开始建造纹理。

 

       // Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit

       if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))

       {

              Status=TRUE;                                                // Set The Status To TRUE

 

 

现在我们已经把图像数据读入了 TextureImage[0] 我们将用这个数据来建造纹理。 下面的第一行代码glGenTextures(1, &texture[0]) 用于获得一个未使用的纹理名称。

第二行代码glBindTexture(GL_TEXTURE_2D, texture[0]) 绑定一个纹理名称到一个纹理对象。

(译注:glBindTexture 即创建纹理对象, 又绑定纹理对象, 又使用纹理对象。 详细内容请看OpenGL红皮书)

 

 

              glGenTextures(1, &texture[0]);                             // Create The Texture

 

              // Typical Texture Generation Using Data From The Bitmap

              glBindTexture(GL_TEXTURE_2D, texture[0]);

 

 

现在我们建造实际的纹理(译注:这一步是指定纹理数据) 下面这行代码告知OpenGL 我们将定义一个2D纹理。 0代表图像的详细级别, 这通常为0 (译注:这与多纹理贴图有关) 3 指定数据成分, 因为我们的图像是红,绿,蓝组成的, 所以为3 TextureImage[0]->sizeX 是纹理宽度, 如果你知道具体的宽度数值, 可以直接写在这儿, 不过我们这样做更简单。 TextureImage[0]->sizeY 是纹理高度。 0 是纹理边界, 通常为 0 GL_RGB 告知 OpenGL 我们的图像数据是红,绿,蓝顺序存储的。GL_UNSIGNED_BYTE 表示图像的数据类型是8位无符号整数。 最后TextureImage[0]->data 指定纹理数据, 在这里指向TextureImage[0] 中保存的数据。

(译注:详细内容请看OpenGL红皮书)

 

 

              // Generate The Texture

              glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

 

 

下面两行代码分别用于设置在放大 (GL_TEXTURE_MAG_FILTER) 和缩小 (GL_TEXTURE_MIN_FILTER) 纹理贴图的时候所使用的过滤。 我通常将它们都设置为GL_LINEAR 这使纹理贴图在距离屏幕很远和很近的时候都能看起来很平滑。 当然, 使用 GL_LINEAR 是要牺牲一些效率的, 所以如果你的系统较慢, 你可以使用 GL_NEAREST 不过使用 GL_NEAREST 可能会出现一些锯齿。 当然你可以把它们结合起来使用, 放大用一种, 缩小用一种。

 

              glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);// Linear Filtering

              glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);// Linear Filtering

       }

 

 

现在我们释放掉存储位图数据的所有内存。 首先检查是否有位图保存在TextureImage[0] 有的话检查是否有数据, 有即释放掉, 最后释放掉图像结构, 确保所有内存都释放干净了。

 

       if (TextureImage[0])                                              // If Texture Exists

       {

              if (TextureImage[0]->data)                                   // If Texture Image Exists

              {

                     free(TextureImage[0]->data);                       // Free The Texture Image Memory

              }

 

              free(TextureImage[0]);                                         // Free The Image Structure

       }

 

 

最后返回状态。 如果一切顺利, 变量Status 将为 TRUE 否则为 FALSE

 

       return Status;                                                       // Return The Status

}

 

 

InitGL中我添加了几行新的代码。 第一行 if (!LoadGLTextures()) 调用上面的代码, 读取位图并建造纹理。 如果由于一些原因LoadGLTextures() 失败了, 那么下面一行代码将返回 FALSE 如果读入纹理成功, 那么我们就启用(激活)纹理映射。 如果你忘记了启用纹理映射, 物体看起来将是白色的。

 

int InitGL(GLvoid)                                                  // All Setup For OpenGL Goes Here

{

       if (!LoadGLTextures())                                           // Jump To Texture Loading Routine ( NEW )

       {

              return FALSE;                                                 // If Texture Didn't Load Return FALSE ( NEW )

       }

 

       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

       return TRUE;                                                  // Initialization Went OK

}

 

 

现在我们绘制贴图立方体。 头两行代码都是原来的代码, glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 用于清除屏幕为指定颜色和清除深度缓冲区, 在这个例子中屏幕将被清除为黑色。 glLoadIdentity() 重置视图(译注:模型视图矩阵)

 

int DrawGLScene(GLvoid)                                                    // Here's Where We Do All The Drawing

{

       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                   // Clear Screen And Depth Buffer

       glLoadIdentity();                                            // Reset The Current Matrix

       glTranslatef(0.0f,0.0f,-5.0f);                           // Move Into The Screen 5 Units

 

 

接下来的3行代码用于在xyz轴上旋转立方体, 旋转的角度有分别由变量xrotyrot zrot 控制。

 

       glRotatef(xrot,1.0f,0.0f,0.0f);                                       // Rotate On The X Axis

       glRotatef(yrot,0.0f,1.0f,0.0f);                                       // Rotate On The Y Axis

       glRotatef(zrot,0.0f,0.0f,1.0f);                                       // Rotate On The Z Axis

 

 

下面一行代码用于选择我们要使用的纹理。 如果想变换纹理, 就需要绑定另一个纹理。 有一点很重要, 那就是绑定纹理操作不能在glBegin() glEnd() 之间进行。 请注意我们是如何使用glBindTextures 来创建和选择一个指定的纹理的。

 

       glBindTexture(GL_TEXTURE_2D, texture[0]);                      // Select Our Texture

 

 

想要适当地映射一个纹理(贴图)到一个四边形上, 你必须确保它们的右上角,左上角,右下角和左下角都能正确地一一对应, 否则纹理有可能被倒置过来, 也有可能歪斜了, 或者根本没贴上。

 

glTexCoord2f 的第一个参数是x坐标(译注:纹理坐标。为了与物体坐标相区分,纹理坐标一般使用字母st代替xy) 0.0f 就是纹理的左边, 0.5f 就是纹理的中间, 1.0f 就是纹理的右边。glTexCoord2f 的第二个参数是y坐标(译注:纹理坐标) 0.0f 就是纹理的底边, 0.5f 是中间, 1.0f 就是纹理的顶边(最上边)

(译注:纹理坐标范围是01,详细请看红皮书)

 

现在我们知道, 纹理左上角的坐标是0.0f, 1.0f 而四边形左上角的顶点坐标是 -1.0f, 1.0f 然后我们要做的就是匹配四边形剩下的3个角。

 

你可以试着修改glTexCoord2f 的参数, 1.0f 改为 0.5f 只绘制纹理的左半部分(0.0f0.5f) 0.0f 改为 0.5f 只绘制它的右半部分(0.5f1.0f)

 

       glBegin(GL_QUADS);

              // Front Face

              glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);      // Bottom Left Of The Texture and Quad

              glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);      // Bottom Right Of The Texture and Quad

              glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);      // Top Right Of The Texture and Quad

              glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);     // Top Left Of The Texture and Quad

              // Back Face

              glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);       // Bottom Right Of The Texture and Quad

              glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);      // Top Right Of The Texture and Quad

              glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);      // Top Left Of The Texture and Quad

              glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);       // Bottom Left Of The Texture and Quad

              // Top Face

              glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);      // Top Left Of The Texture and Quad

              glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);     // Bottom Left Of The Texture and Quad

              glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);      // Bottom Right Of The Texture and Quad

              glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);      // Top Right Of The Texture and Quad

              // Bottom Face

              glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);       // Top Right Of The Texture and Quad

              glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);       // Top Left Of The Texture and Quad

              glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);      // Bottom Left Of The Texture and Quad

              glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);      // Bottom Right Of The Texture and Quad

              // Right face

              glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);       // Bottom Right Of The Texture and Quad

              glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);      // Top Right Of The Texture and Quad

              glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);      // Top Left Of The Texture and Quad

              glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);      // Bottom Left Of The Texture and Quad

              // Left Face

              glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);       // Bottom Left Of The Texture and Quad

              glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);      // Bottom Right Of The Texture and Quad

              glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);     // Top Right Of The Texture and Quad

              glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);      // Top Left Of The Texture and Quad

       glEnd();

 

 

现在我们给xrotyrot zrot 增量。 你可以试着修改这些增量值, 让立方体旋转的更快或者更慢。 或者, +改为-,让其向反方向旋转。

 

       xrot+=0.3f;                                                    // X Axis Rotation

       yrot+=0.2f;                                                    // Y Axis Rotation

       zrot+=0.4f;                                                    // Z Axis Rotation

       return true;                                                    // Keep Going

}

 

 

你应该已经较好地理解了纹理映射, 可以给自己的四边形贴图了。 如果你对2D纹理映射感觉有了信心, 你可以试着给立方体的6个面映射不同的纹理贴图。

 

如果你理解了纹理坐标, 那么纹理映射是不难理解的。 如果此篇教程的某些地方你不明白, 告诉我, 我会重写那一部分教程,或者,在email里给你答复。 祝你贴图愉快:)

 

Jeff Molofee (NeHe)

 

 

(译著) 作者在纹理坐标这个关键的地方并没有做多少叙述,但是由于这是非常基础的,所以并无大碍。 如果你发现了什么问题或者疏漏, 请即时反馈给我, 这样我就能做出相应的补救或者更正。 十分欢迎你的支持和鼓励, 那将会使我更有动力。

posted on 2006-01-19 23:52 zmj 阅读(5082) 评论(1)  编辑 收藏 引用

评论

# re: OpenGL Texture Mapping 2011-05-08 17:46 丙丁丙丁

六个面贴六个图的详细的程序可以再写一下吗?  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理