Jeff Molofee(NeHe)的OpenGL教程Lesson: 38 Loading Textures From A Resource File & Texturing Triangles
欢迎来到NeHe教程第38课。离上节课的写作已经有些时日了,加上写了一整天的code,也许笔头已经开始生锈了 :)
现在你已经学会了如何做方格贴图,如何读入bitmap及各种光栅图像...那么如何做三角形贴图,又如何在.exe文件中体现你的纹理呢?
我每每被问及这两个问题,可是一旦你看到他们是多么简单,你就会大骂自己居然没有想到过 :)
我不会事无巨细地解释每一个细节,只需给你一些抓图,就明白了。我将基于最新的code,请在主页"NeHeGL I Basecode"下或者这张网页最下面下载。
首先,我们把图像加载入资源文件。我向大家已经知道怎么做了,只是,你忽略了几步,于是值得到一些无用的资源文件。里面有bitmap文件,却无法使用。
还记得吧?我们使用Visual C++ 6.0 做的。如果你使用其它工具,这页教材关于资源的部分(尤其是那些图)完全不适用。
* 暂时你只能用24bit BMP 图像。如果读8bit BMP文件要写很多额外的code。我很希望听到你们谁有更小的/更好的loader。我这里的读入8bit 和 24bit BMP 的code实在臃肿。用LoadImage就可以。
打开文件,点击“插入”菜单,选“资源”
然后选择你要插入的资源类型BITMAP文件,单击"插入"
然后是文件窗口,进入DATA目录,选中三个图形文件(用Ctrl啦)然后点“读入”。注意文件类型是否正确。
接下来会弹出三次警告(一个文件一次),说读入正确,但该文件不能被浏览或编辑,因为它有多于256种颜色。没什么的!
一旦所有图形都调入,将会出现一个列表。每个图分配有一个ID,每个ID都是IDB_BITMAP打头的,然后数字1-3。你要是懒得改,就不用管它了。不过我们还都比较勤快!
右健单击每个ID,选"属性",然后重命名,使之与文件名匹配。就像我图片上那样。
接下来,选“文件--〉全部保存”。你刚刚创建一个新的资源文件,所以Windows会问你取什么名字。你随便拉,也可以叫"lesson38.rc" , 然后保存。
到此为止,你有了一个资源文件,里面全是保存在硬盘上的Bitmap 图形文件,要使用这些文件,你还需要完成一系列步骤。
接下来该把资源文件加到你自己的项目里面了。选“项目--〉添加到项目--〉文件”
选择resorce.h文件和资源文件Lesson38.rc(用Ctrl)
最后确认资源文件Lesson38.rc放入RESOURCE FILES文件夹。就像上面图片里那样,点击并拖入RESOURCE FILES文件夹就好了。
移动之后选“文件--〉全部保存”,然后文件部分就好了。好多的图阿:)
然后我们开始code的部分。下面一段最重要的一行是#include "resource.h".没有这行,编译的时候就会有无数未定义变量的错误。resource.h文件定义了资源文件里的对象。所以要从IDB_BUTTERFLY1里面读取数据的话,最好include这个头文件 !
#include <windows.h> // Header File For Windows #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 #include "NeHeGL.h" // Header File For NeHeGL #include "resource.h" // Header File For Resource (*IMPORTANT*) #pragma comment( lib, "opengl32.lib" ) // Search For OpenGL32.lib While Linking #pragma comment( lib, "glu32.lib" ) // Search For GLu32.lib While Linking #pragma comment( lib, "glaux.lib" ) // Search For GLaux.lib While Linking
#ifndef CDS_FULLSCREEN // CDS_FULLSCREEN Is Not Defined By Some #define CDS_FULLSCREEN 4 // Compilers. By Defining It This Way, #endif // We Can Avoid Errors GL_Window* g_window;
Keys* g_keys;
下面一段第一行分配三个纹理所需空间,接下来的结构体用于保存关于约50个在屏幕上运动的物体的信息。
tex将跟踪每个物体所用纹理,x是物体的x坐标,y是y坐标,z,z坐标,yi是一个随机数用来控制物体下落速度,
spinz用来控制沿z轴的旋转,spinzi是另一个随机树,记录旋转速度。flap用来控制物体的翅膀(一会在解释这个)随机数fi控制翅膀拍打的速度。 We create 50 instances of obj[ ] based on the object structure.
// User Defined Variables GLuint texture[3]; // Storage For 3 Textures struct object // Create A Structure Called Object { int tex; // Integer Used To Select Our Texture float x; // X Position float y; // Y Position float z; // Z Position float yi; // Y Increase Speed (Fall Speed) float spinz; // Z Axis Spin float spinzi; // Z Axis Spin Speed float flap; // Flapping Triangles :) float fi; // Flap Direction (Increase Value) };
object obj[50]; // Create 50 Objects Using The Object Structure
下面一段代码是物体obj[loop]的初始化,loop从0到49(表示50个物体中的一个)。首先是随机纹理从0到2表示
一个随机着色的蝴蝶。x坐标随机的取-17.0f到+17.0f之间的值,y取18.0f,也就是从屏幕的上面一点点开始,
这样一开始时看不到物体的。z也是-10.0f到-40.f之间的随机数,spinzi取-1.0f到1.0f之间。flap取翅膀中心
位置,为0.0f。最后拍打速度fi和下落速度yi也是随机的。
void SetObject(int loop) // Sets The Initial Value Of Each Object (Random) { obj[loop].tex=rand()%3; // Texture Can Be One Of 3 Textures obj[loop].x=rand()%34-17.0f; // Random x Value From -17.0f To 17.0f obj[loop].y=18.0f; // Set y Position To 18 (Off Top Of Screen) obj[loop].z=-((rand()%30000/1000.0f)+10.0f); // z Is A Random Value From -10.0f To -40.0f obj[loop].spinzi=(rand()%10000)/5000.0f-1.0f; // spinzi Is A Random Value From -1.0f To 1.0f obj[loop].flap=0.0f; // flap Starts Off At 0.0f; obj[loop].fi=0.05f+(rand()%100)/1000.0f; // fi Is A Random Value From 0.05f To 0.15f obj[loop].yi=0.001f+(rand()%1000)/10000.0f; // yi Is A Random Value From 0.001f To 0.101f }
这回该到了最有意思的地方了。从资源文件中读入bitmap,转为纹理。hBMP是指向这个bitmap文件的指针,
它将告诉我们的程序从哪里读取数据。BMP是一个bitmap结构体,我们把从资源文件中读取的数据保存在里面。
第三行是告诉我们的程序我们将使用哪些ID:IDB_BUTTERFLY1,IDB_BUTTERFLY2,IDB_BUTTERFLY3。要用更多
的图像的话,只需增加资源文件中的图像,并在Texture[]中增加新的ID。
void LoadGLTextures() // Creates Textures From Bitmaps In The Resource File { HBITMAP hBMP; // Handle Of The Bitmap BITMAP BMP; // Bitmap Structure // The ID Of The 3 Bitmap Images We Want To Load From The Resource File byte Texture[]={ IDB_BUTTERFLY1, IDB_BUTTERFLY2, IDB_BUTTERFLY3 };
下面一行使用sizeof(Texture)来计算要创建多少个纹理。我们有3个ID,也就是3个纹理。
sizeof(Texture)也会在后面主循环中用到。
glGenTextures(sizeof(Texture), &texture[0]); // Generate 3 Textures (sizeof(Texture)=3 ID’s) for (int loop=0; loop<sizeof(Texture); loop++) //Loop Through All The ID’s (Bitmap Images) { LoadImage需要如下参数:GetModuleHandle(NULL)-指向实例的句柄,MAKEINTRESOURCE(Texture[loop])-把Texture[loop]从整型转为一个资
源值,也就是要读的图形文件。IMAGE_BITMAP-告诉我们要读的是一个bitmap文件。
接下来两个参数(0,0)是读入图像的高度和宽度像素数,使用默认大小就设为0。
最后一个参数(LR_CREATEDIBSECTION)返回DIB section bitmap??这是一个没有保存颜色信息的bitmap。也正是我们需要的。
hBMP 指向从LoadImage()读入的bitmap数据。
hBMP=(HBITMAP)LoadImage(GetModuleHandle(NULL),MAKEINTRESOURCE(Texture[loop]), IMAGE_BITMAP, 0, 0,
LR_CREATEDIBSECTION);
检查指针hBMP是否有效,即指向有用数据。如果没有指向任何数据,也可以弹出错误提示。
如果数据存在,用GetObject()从hBMP取得数据(sizeof(BMP))并存储在BMP中。
glPixelStorei告诉OpenGL这些数据是以word alignments存储的,也就是每像素4字节。
绑定纹理,设置滤波方式为GL_LINEAR_MIPMAP_LINEAR(又好又光滑),然后生成纹理。
注意到我们使用BMP.bmWidth和BMP.bmHeight获取图像的高度和宽度。并用GL_BGR_EXT交换红蓝,实际使用的资源数据是从BMP.bmBits中取得的
。
最后删除bitmap对象,释放所有与之相联系的系统资源空间。
if (hBMP) // Does The Bitmap Exist? { // If So... GetObject(hBMP,sizeof(BMP), &BMP); // Get The Object // hBMP: Handle To Graphics Object // sizeof(BMP): Size Of Buffer For Object Information // Buffer For Object Information glPixelStorei(GL_UNPACK_ALIGNMENT,4); // Pixel Storage Mode (Word Alignment / 4 Bytes) glBindTexture(GL_TEXTURE_2D, texture[loop]); // Bind Our Texture glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); // Mipmap Linear
Filtering
// Generate Mipmapped Texture (3 Bytes, Width, Height And Data From The BMP) gluBuild2DMipmaps(GL_TEXTURE_2D, 3, BMP.bmWidth, BMP.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE,
BMP.bmBits); DeleteObject(hBMP); // Delete The Bitmap Object } } }
init code没有什么新鲜的,只是增加了LoadGLTextures()调用上面的code。清屏的颜色是黑色,不进行深度检测,这样比较快。启用纹理映
射和混色效果。
BOOL Initialize (GL_Window* window, Keys* keys) // Any GL Init Code & User Initialiazation Goes Here g_window = window; g_keys = keys; // Start Of User Initialization LoadGLTextures(); // Load The Textures From Our Resource File glClearColor (0.0f, 0.0f, 0.0f, 0.5f); // Black Background glClearDepth (1.0f); // Depth Buffer Setup glDepthFunc (GL_LEQUAL); // The Type Of Depth Testing (Less Or Equal) glDisable(GL_DEPTH_TEST); // Disable Depth Testing glShadeModel (GL_SMOOTH); // Select Smooth Shading glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Set Perspective Calculations To Most Accurate glEnable(GL_TEXTURE_2D); // Enable Texture Mapping glBlendFunc(GL_ONE,GL_SRC_ALPHA); // Set Blending Mode (Cheap / Quick) glEnable(GL_BLEND); // Enable Blending
下面的循环紧接着初始化50个对象,不然它们会自己出现在屏幕中央相同的位置。
for (int loop=0; loop<50; loop++) // Loop To Initialize 50 Objects { SetObject(loop); // Call SetObject To Assign New Random Values }
return TRUE; // Return TRUE (Initialization Successful) }
void Deinitialize (void) // Any User DeInitialization Goes Here { }
void Update (DWORD milliseconds) // Perform Motion Updates Here { if (g_keys->keyDown [VK_ESCAPE] == TRUE) // Is ESC Being Pressed? { TerminateApplication (g_window); // Terminate The Program }
if (g_keys->keyDown [VK_F1] == TRUE) // Is F1 Being Pressed? { ToggleFullscreen (g_window); // Toggle Fullscreen Mode } }
接下来看看绘制代码。在这部分我将讲解如何用尽可能简单的方式将一个图像映到两个三角形上。有些人认为有理由相信,一个图像到三角形
上的单一映射是不可能的。
实际上,你可以轻而易举地将图像映到任何形状的区域内。使得图像与边界匹配或者完全不考虑形式。根本没关系的。(译者:我想作者的意
思是,从长方形到三角形的解析影射是不存在的,但不考虑那么多的话,任意形状之间的连续影射总是可以存在的。他说的使纹理与边界匹配
,大概是指某一种参数化的方法,简单地说使得扭曲最小。)
首先清屏,循环润色50个蝴蝶对象。
void Draw (void) // Draw The Scene { glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
for (int loop=0; loop<50; loop++) // Loop Of 50 (Draw 50 Objects) {
调用glLoadIdentify()重置投影矩阵,然后选择对象的纹理。用glTranslatef()为蝴蝶定位,然后沿x轴旋转45度。使之向观众微略倾斜,这
样比较有立体感。最后沿z轴旋转,蝴蝶就旋转下落了。
glLoadIdentity (); // Reset The Modelview Matrix glBindTexture(GL_TEXTURE_2D, texture[obj[loop].tex]); // Bind Our Texture glTranslatef(obj[loop].x,obj[loop].y,obj[loop].z); // Position The Object glRotatef(45.0f,1.0f,0.0f,0.0f); // Rotate On The X-Axis glRotatef((obj[loop].spinz),0.0f,0.0f,1.0f); // Spin On The Z-Axis
其实到三角形上的映射和到方形上并没有很大区别。只是你只有三个定点,要小心一点。
下面的code中,我们将会值第一个三角形。从一个设想的方形的右上角开始,到左上角,再到左下角。润色的结果像下面这样:
注意半个蝴蝶出现了。另外半个出现在第二个三角形里。同样地将三个纹理坐标与顶点坐标非别对应,这给出充分的信息定义一个三角形上的
映射。
glBegin(GL_TRIANGLES); // Begin Drawing Triangles // First Triangle glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f); // Point 1 (Top Right) glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f, obj[loop].flap); // Point 2 (Top Left) glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f); // Point 3 (Bottom Left)
下面的code润色另一半。同上,只是我们的三角变成了从右上到左下,再到右下。
第一个三角形的第二点和第二个三角形的第三点(也就是翅膀的尖端)在z方向往复运动(即z=-1.0f和1.0f之间),两个三角形沿着蝴蝶的身
体折叠起来,产生拍打的效果,简易可行。
// Second Triangle glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f); // Point 1 (Top Right) glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f); // Point 2 (Bottom Left) glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f,-1.0f, obj[loop].flap); // Point 3 (Bottom Right)
glEnd(); // Done Drawing Triangles
下面一段通过从obj[loop].y中递减obj[loop].yi使蝴蝶自上而下运动。spinz值递增spinzi(可正可负)flap递增fi.fi的正负取决于翅膀向
上还是向下运动。
obj[loop].y-=obj[loop].yi; // Move Object Down The Screen obj[loop].spinz+=obj[loop].spinzi; // Increase Z Rotation By spinzi obj[loop].flap+=obj[loop].fi; // Increase flap Value By fi
当蝴蝶向下运行时,需要检查是否越出屏幕,如果是,就调用SetObject(loop)来给蝴蝶赋新的纹理,新下落速度等。
if (obj[loop].y<-18.0f) // Is Object Off The Screen? { SetObject(loop); // If So, Reassign New Values } 翅膀拍打的时候,还要检查flap是否小于-1.0f或大于1.0f,如果是,令fi=-fi,以改变运动方向。Sleep(15)是用来减缓运行速度,每帧15毫
秒。在我朋友的机器上,这让蝴蝶疯狂的飞舞。不过我懒得改了:)
if ((obj[loop].flap>1.0f) || (obj[loop].flap<-1.0f)) // Time To Change Flap Direction? { obj[loop].fi=-obj[loop].fi; // Change Direction By Making fi = -fi } }
Sleep(15); // Create A Short Delay (15 Milliseconds)
glFlush (); // Flush The GL Rendering Pipeline }
希望你在这一课学的开心。也希望通过这一课,从资源文件里读取纹理,和三角形映射的过程变得比较容易理解。我花五分钟又冲读了一遍,
感觉还好。如果你还有什么问题,尽管问。我希望我的讲义尽可能好,因此期待您的任何回应。
Jeff Molofee (NeHe)
* DOWNLOAD Visual C++ Code For This Lesson. * DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Christian Kindahl ) * DOWNLOAD Code Warrior 5.3 Code For This Lesson. ( Conversion by Scott Lupton ) * DOWNLOAD Dev C++ Code For This Lesson. ( Conversion by Warren Moore ) * DOWNLOAD Game GLUT Code For This Lesson. ( Conversion by Alexandre Ribeiro de S?/a> ) * DOWNLOAD LCC Win32 Code For This Lesson. ( Conversion by Robert Wishlaw ) * DOWNLOAD Mac OS X/Cocoa Code For This Lesson. ( Conversion by Bryan Blackburn ) * DOWNLOAD Visual Studio .NET Code For This Lesson. ( Conversion by Grant James ) * DOWNLOAD Lesson 38 - Enhanced (Masking, Sorting, Keyboard - NeHe). * DOWNLOAD Lesson 38 - Screensaver by Brian Hunsucker.
http://www.owlei.com/DancingWind/Course/Tutorial_38.htm