欢迎来到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>
#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glaux.h>
#include "NeHeGL.h"
#include "resource.h" // 资源文件的头文件
#pragma comment( lib, "opengl32.lib" )
#pragma comment( lib, "glu32.lib" )
#pragma comment( lib, "glaux.lib" )
GL_Window* g_window;
Keys* g_keys;
下面一段第一行分配三个纹理所需空间,接下来的结构体用于保存关于约50个在屏幕上运动的物体的信息。
tex将跟踪每个物体所用纹理,x是物体的x坐标,y是y坐标,z,z坐标,yi是一个随机数用来控制物体下落速度,
spinz用来控制沿z轴的旋转,spinzi是另一个随机树,记录旋转速度。flap用来控制物体的翅膀(一会在解释这个)随机数fi控制翅膀拍打的速度。
// 定义三个保存纹理变量的ID
GLuint texture[3]; // 保存三个纹理
struct object // 定义一个物体
{
int tex; // 纹理值
float x; // 位置
float y;
float z;
float yi; // 速度
float spinz; // 沿Z轴旋转的角度和速度
float spinzi;
float flap; // 是否翻转三角形
float fi;
};
object obj[50]; // 创建50个物体
下面一段代码是物体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) // 循环设置50个物体
{
obj[loop].tex=rand()%3; // 纹理
obj[loop].x=rand()%34-17.0f; // 位置
obj[loop].y=18.0f;
obj[loop].z=-((rand()%30000/1000.0f)+10.0f);
obj[loop].spinzi=(rand()%10000)/5000.0f-1.0f; // 旋转
obj[loop].flap=0.0f;
obj[loop].fi=0.05f+(rand()%100)/1000.0f;
obj[loop].yi=0.001f+(rand()%1000)/10000.0f;
}
这回该到了最有意思的地方了。从资源文件中读入bitmap,转为纹理。hBMP是指向这个bitmap文件的指针,
它将告诉我们的程序从哪里读取数据。BMP是一个bitmap结构体,我们把从资源文件中读取的数据保存在里面。
第三行是告诉我们的程序我们将使用哪些ID:IDB_BUTTERFLY1,IDB_BUTTERFLY2,IDB_BUTTERFLY3。要用更多
的图像的话,只需增加资源文件中的图像,并在Texture[]中增加新的ID。
void LoadGLTextures() // 资源文件中读入bitmap,转为纹理
{
HBITMAP hBMP; // 位图句柄
BITMAP BMP; // 位图结构
// 纹理句柄
byte Texture[]={ IDB_BUTTERFLY1, IDB_BUTTERFLY2, IDB_BUTTERFLY3 };
下面一行使用sizeof(Texture)来计算要创建多少个纹理。我们有3个ID,也就是3个纹理。
glGenTextures(sizeof(Texture), &texture[0]); // 创建三个纹理
for (int loop=0; loop<sizeof(Texture); loop++) // 循环载入所有的位图
{
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) // 位图是否存在
{ // 存在
GetObject(hBMP,sizeof(BMP), &BMP); // 获得位图
glPixelStorei(GL_UNPACK_ALIGNMENT,4); // 以四字节方式对其内存
glBindTexture(GL_TEXTURE_2D, texture[loop]); // 绑定位图
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 设置纹理过滤器
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
// 创建纹理
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, BMP.bmWidth, BMP.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits);
DeleteObject(hBMP); // 删除位图对象
}
}
}
init code没有什么新鲜的,只是增加了LoadGLTextures()调用上面的code。清屏的颜色是黑色,不进行深度检测,这样比较快。启用纹理映
射和混色效果。
BOOL Initialize (GL_Window* window, Keys* keys) // 初始化
{
g_window = window;
g_keys = keys;
LoadGLTextures(); //载入纹理
glClearColor (0.0f, 0.0f, 0.0f, 0.5f); // 设置背景
glClearDepth (1.0f);
glDepthFunc (GL_LEQUAL);
glDisable(GL_DEPTH_TEST); // 启用深度测试
glShadeModel (GL_SMOOTH);
glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glEnable(GL_TEXTURE_2D); // 启用2D纹理
glBlendFunc(GL_ONE,GL_SRC_ALPHA); // 使用混合
glEnable(GL_BLEND);
初始化所有的物体
for (int loop=0; loop<50; loop++)
{
SetObject(loop);
}
return TRUE; // 成功返回
}
void Deinitialize (void)
{
}
void Update (DWORD milliseconds) // 更新,执行动画
{
if (g_keys->keyDown [VK_ESCAPE] == TRUE) // 按ESC退出
{
TerminateApplication (g_window);
}
if (g_keys->keyDown [VK_F1] == TRUE) // 按F1切换显示模式
{
ToggleFullscreen (g_window);
}
}
接下来看看绘制代码。在这部分我将讲解如何用尽可能简单的方式将一个图像映到两个三角形上。有些人认为有理由相信,一个图像到三角形
上的单一映射是不可能的。
实际上,你可以轻而易举地将图像映到任何形状的区域内。使得图像与边界匹配或者完全不考虑形式。根本没关系的。(译者:我想作者的意
思是,从长方形到三角形的解析影射是不存在的,但不考虑那么多的话,任意形状之间的连续影射总是可以存在的。他说的使纹理与边界匹配
,大概是指某一种参数化的方法,简单地说使得扭曲最小。)
首先清屏,循环润色50个蝴蝶对象。
void Draw (void) // 绘制场景
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
for (int loop=0; loop<50; loop++)
{
调用glLoadIdentify()重置投影矩阵,然后选择对象的纹理。用glTranslatef()为蝴蝶定位,然后沿x轴旋转45度。使之向观众微略倾斜,这
样比较有立体感。最后沿z轴旋转,蝴蝶就旋转下落了。
glLoadIdentity (); // 重置矩阵
glBindTexture(GL_TEXTURE_2D, texture[obj[loop].tex]); // 绑定纹理
glTranslatef(obj[loop].x,obj[loop].y,obj[loop].z); // 绘制物体
glRotatef(45.0f,1.0f,0.0f,0.0f);
glRotatef((obj[loop].spinz),0.0f,0.0f,1.0f);
其实到三角形上的映射和到方形上并没有很大区别。只是你只有三个定点,要小心一点。
下面的code中,我们将会值第一个三角形。从一个设想的方形的右上角开始,到左上角,再到左下角。润色的结果像下面这样:
注意半个蝴蝶出现了。另外半个出现在第二个三角形里。同样地将三个纹理坐标与顶点坐标非别对应,这给出充分的信息定义一个三角形上的映射。
glBegin(GL_TRIANGLES);
glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f, obj[loop].flap);
glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
下面的code润色另一半。同上,只是我们的三角变成了从右上到左下,再到右下。
第一个三角形的第二点和第二个三角形的第三点(也就是翅膀的尖端)在z方向往复运动(即z=-1.0f和1.0f之间),两个三角形沿着蝴蝶的身
体折叠起来,产生拍打的效果,简易可行。
glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f,-1.0f, obj[loop].flap);
glEnd();
下面一段通过从obj[loop].y中递减obj[loop].yi使蝴蝶自上而下运动。spinz值递增spinzi(可正可负)flap递增fi.fi的正负取决于翅膀向
上还是向下运动。
//移动,选择图像
obj[loop].y-=obj[loop].yi;
obj[loop].spinz+=obj[loop].spinzi;
obj[loop].flap+=obj[loop].fi;
当蝴蝶向下运行时,需要检查是否越出屏幕,如果是,就调用SetObject(loop)来给蝴蝶赋新的纹理,新下落速度等。
if (obj[loop].y<-18.0f) //判断是否超出了屏幕,如果是重置它
{
SetObject(loop);
}
翅膀拍打的时候,还要检查flap是否小于-1.0f或大于1.0f,如果是,令fi=-fi,以改变运动方向。Sleep(15)是用来减缓运行速度,每帧15毫
秒。在我朋友的机器上,这让蝴蝶疯狂的飞舞。不过我懒得改了:)
if ((obj[loop].flap>1.0f) || (obj[loop].flap<-1.0f))
{
obj[loop].fi=-obj[loop].fi;
}
}
Sleep(15);
glFlush ();
}
希望你在这一课学的开心。也希望通过这一课,从资源文件里读取纹理,和三角形映射的过程变得比较容易理解。我花五分钟又冲读了一遍,
感觉还好。如果你还有什么问题,尽管问。我希望我的讲义尽可能好,因此期待您的任何回应。