原文链接:TetroGL: An OpenGL Game Tutorial in C++ for Win32 Platforms - Part 2
在这个系列的第一部分中,作者介绍了窗口的创建以及OpenGL环境的创建,在接下来这一部分中,作者将介绍如何处理游戏中的资源以及如何显示简单的动画
简介
这个系列的第一篇文章关注于窗口的创建和OpenGL环境的创建,本文将有趣的多,因为我们将尝试加载显示图片文件,并且显示一些动画效果.你将会看到如何才能有效地操纵这些资源.当然本文完成的项目还不是一个游戏,因为它还没有加入任何游戏逻辑,它唯一能做的仅仅是在屏幕上移动人物角色,并且用动画的效果显示(没有实现碰撞检测)
文件的组织
首先来考虑如何更好地组织文件资源.作者一般会创建一个src文件夹来放置所有的源文件(.h和.cpp),一个bin文件夹来放置最终的可执行文件和所有所需要的资源,一个obj文件夹
用来放置编译所得到的中间文件,一个dependencies文件夹放置用到的第三方库.如果你有许多资源(图片,音乐,配置文件等),你甚至可以将bin文件夹进一步划分为子文件夹.
现在我们就来按照上面的文件组织形式来更改项目设置.对于源文件,只需要将它们复制到src文件夹中,并将其加入项目就行.为了配置输出文件夹和中间文件夹,更改如下图:
$(SolutionDir) 和$(ConfigurationName) 是预先定义的宏.前一个指向解决方案所在文件夹,后一个指向当前活动配置(debug or release):在obj文件夹中,会创建出两个子文件夹,一个配置一个文件夹
加载图片
很不幸,OpenGL对于加载图片没有提供任何帮助.因此我们必须借助第三方库的帮助.有很多第三方库可供选择,作者提供了两个建议: DevIL 和FreeImage.DevIL更适合于OpenGL,因此作者选择了它.
首先要做的是将所需要的DevIL文件拷贝到dependencies文件夹中:首先创建一个子文件夹DevIL,并将DevIL官网上的文件拷贝至此.要正确地使用它,我们必须修改一个文件的名字:在”include/IL”文件夹中,有一个名为config.h.win的文件,将其重命名为config.h.然后拷贝DevIL.dll到你的bin文件夹中,因为它将会被你的可执行文件使用到.
然后我们必须在项目属性中进行配置,以便使用DevIL.如下图所示:
这将会告诉编译器到哪里去寻找所需要的DevIL头文件,这样设置,我们就可以不必提供DevIL头文件的全路径.
上图配置就告诉链接器到哪里去寻找附加的文件夹(这个文件夹中包含了要链接的库文件).
上图配置会告诉编译器此项目必须链接DevIL库和OpenGL库.
资源管理
现在使用DevIL的环境已经搭建好了,我们现在开始加载一些图片并显示它们.但在此之前,我们先考虑下如何更有效地管理这些资源文件.假设我们需要显示一棵树,它包含在个名为tree.png的文件中,最暴力的方法是简单地加载文件并保存在内存中,这样在每次重绘帧时可以重用它.这种方法看起来不错,但它有一个小问题:假设我们现在需要显示此树的次数超过一次,那么我们就必须几次在内存中加载纹理文件,而这显然是低效的.我们必须要想一个办法,即使我们在不同位置的代码中也能使用同一份纹理文件.这通过将加载资源文件代理给一个特定的类:纹理文件管理者就可以轻松地解决.让我们首先来看看这个类:
CTextureManager资源管理类
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->#include"Texture.h"
#include<string>
#include<map>
//Thetexturemanageravoidasametexturetobeloadedmultiple
//times.Itkeepsamapcontainingallthealreadyloadedtextures.
classCTextureManager
{
public:
//Loadsatexturespecifiedbyitsfilename.Ifthetextureisnot
//loadedalready,thetexturemanagerwillloadit,storeitand
//returnit.Otherwiseitsimplyreturnstheexistingone.
CTexture*GetTexture(conststd::string&strTextName);
//Releasethetexturespecifiedbyitsfilename.Returnstrueif
//thetexturewasfound,otherwisefalse.
boolReleaseTexture(conststd::string&strTextName);
//Returnsthesingleinstanceofthetexturemanager.
//Themanagerisimplementedasasingleton.
staticCTextureManager*GetInstance();
protected:
//Bothconstructoranddestructorareprotectedtomake
//itimpossibletocreateaninstancedirectly.
CTextureManager();
~CTextureManager();
private:
typedefstd::map<std::string,CTexture*>TTextureMap;
//Themapofalreadyloadedtextures.Thereareindexed
//usingtheirfilename.
TTextureMapm_Textures;//已加载的资源文件映射表
};
这个类是以单例模式实现的.
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->CTextureManager*CTextureManager::GetInstance()
{
//Returnstheuniqueclassinstance.
staticCTextureManagerInstance;
return&Instance;
}
这样就可以拥有一个全局唯一的实例,并且访问它也十分简单:
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->CTexture*pTexture=CTextureManager::GetInstance()->GetTexture("MyTexture.bmp");
这个类的构造函数负责对DevIL库进行初始化:
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->CTextureManager::CTextureManager():m_Textures()
{
//InitializeDevIL
ilInit();
//Setthefirstloadedpointtothe
//upper-leftcorner.
ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
ilEnable(IL_ORIGIN_SET);
}
在调用DevIL库函数前,必须先调用ilInit以便对库进行初始化.此外,我们还需要指明图片如何进行加载:先是左上方.这样做的目的是我们就不需要对纹理图片进行翻转.默认情况下这个选项是禁止的,因此我们需要调用ilEnable(IL_ORIGIN_SET);来使之设置为允许.
现在来看看GetTexture方法:
获取纹理资源
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->CTexture*CTextureManager::GetTexture(conststring&strTextName)
{
//Lookinthemapifthetextureisalreadyloaded.
TTextureMap::const_iteratoriter=m_Textures.find(strTextName);
if(iter!=m_Textures.end())
returniter->second;
//Ifitwasnotfound,trytoloaditfromfile.Iftheload
//failed,deletethetextureandthrowanexception.
CTexture*pNewText=NULL;
try
{
pNewText=newCTexture(strTextName);
}
catch(CException&e)
{
deletepNewText;
throwe;
}
//Storethenewlyloadedtextureandreturnit.
m_Textures[strTextName]=pNewText;
returnpNewText;
}
很简单的实现代码:首先根据给定的文件名在映射表中查找文件是否已经加载进来了,若是则直接返回,否则就从文件中进行加载.待会我们会看到在CTexture类的构造函数中会尝试加载文件,若失败则抛出异常.因此,在纹理文件管理者类中,若捕获到此异常,就删除纹理文件(这是为了避免内存泄露)并且再次抛出异常.若文件加载成功,则将其保存到映射表中(以其文件名作为键值).
此外,我们还提供了释放已加载资源的方法,非常简单的实现:在映射表中查找,若存在就删除它,并且从映射表中移除.
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->boolCTextureManager::ReleaseTexture(conststd::string&strTextName)
{
//Retrievethetexturefromthemap
boolbFound=false;
TTextureMap::iteratoriter=m_Textures.find(strTextName);
if(iter!=m_Textures.end())
{
//Ifitwasfound,wedeleteitandremovethe
//pointerfromthemap.
bFound=true;
if(iter->second)
deleteiter->second;
m_Textures.erase(iter);
}
returnbFound;
}
资源包装类CTexture
CTexture类
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->
#include<Windows.h>
#include"
GL/gl.h"
#include<
string>
classCTextureManager;
//Classthatwrapsinformationaboutatexture.Thisclass
//won'tbeuseddirectlybytheusers.Instead,theywill
//manipulatetheCImageclass.
classCTexture
{
friendclassCTextureManager;
public:
//Specifiesacolorkeytobeusedforthetexture.Thecolor
//specifedasargumentswillbetransparentwhenthetexture
//isrenderedonthescreen.
voidSetColorKey(unsignedcharRed,unsignedcharGreen,unsignedcharBlue);
//Returnsthewidthofthetexture
unsigned
intGetWidth()const{returnm_TextData.nWidth;}
//Returnstheheightofthetexture.
unsigned
intGetHeight()const{returnm_TextData.nHeight;}
//Adds/releaseareferenceforthetexture.WhenReleaseReference
//iscalledanddecreasesthereferencecountto0,thetexture
//isreleasedfromthetexturemanager.voidAddReference();
voidReleaseReference();
//BindthistexturewithopenGL:thistexturebecomes
//the'active'textureinopenGL.
voidBind()const;
protected:
//Constructorwhichtakesthefilenameasargument.
//Itloadsthefileandthrowanexceptioniftheload
//failed.
CTexture(
conststd::string&strFileName);
~CTexture();
private:
//Loadsthetexturefromthespecifedfile.Throwsan
//exceptioniftheloadfailed.
voidLoadFile(conststd::string&strFileName);
//Structurethatcontainstheinformationaboutthetexture.
structSTextureData
{
//Widthofthetexture
unsignedintnWidth;//纹理宽度
//Heightofthetexture
unsignedintnHeight;//纹理高度
//Bytearraycontainingthetexturedata
unsignedchar*pData;//包含纹理数据的字节数组
};
STextureDatam_TextData;
//TheopenGLidassociatedwiththistexture.
mutableGLuintm_glId;
//Referencecountofthenumberofimagesthatstillholdareference
//tothistexture.Whennoimagesreferencethetextureanymore,itis
//released.
intm_iRefCount;//引用计数
//Thefilenamefromwhichthetexturewasloadedfrom.
std::stringm_strTextName;
};
我们可以看到此类的构造函数是受保护的,这是因为只允许CTextureManager类能够创建纹理,这也是为什么将其设为此类的友元类.CTexture类的核心是STextureData结构体,它包含了从文件加载进的所有信息:包含文件数据的字节数组,纹理的宽度和高度.
下面看看究竟是如何加载文件的:
加载资源文件
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->voidCTexture::LoadFile(conststd::string&strFileName)
{
//GenerateanewimageIdandbinditwiththe
//currentimage.
ILuintimgId;
ilGenImages(1,&imgId);
ilBindImage(imgId);
//Loadthefiledatainthecurrentimage.
if(!ilLoadImage(strFileName.c_str()))
{
stringstrError="Failedtoloadfile:"+strFileName;
throwCException(strError);
}
//StorethedatainourSTextureDatastructure.
m_TextData.nWidth=ilGetInteger(IL_IMAGE_WIDTH);
m_TextData.nHeight=ilGetInteger(IL_IMAGE_HEIGHT);
unsignedintsize=m_TextData.nWidth*m_TextData.nHeight*4;//字节数,RGBA类型
m_TextData.pData=newunsignedchar[size];
ilCopyPixels(0,0,0,m_TextData.nWidth,m_TextData.nHeight,
1,IL_RGBA,IL_UNSIGNED_BYTE,m_TextData.pData);
//Finally,deletetheDevILimagedata.
ilDeleteImage(imgId);
}
正如你看到的,我们使用DevIL来加载文件.首先要做的是创建一个新的图片id,并将其绑定到当前图片上.如果你想使用id对某个特定图片进行一些操作时,这是必需的.实际上,我们只需要在删除图片时使用它.然后,我们使用ilLoadImage尝试加载文件.这个函数负责处理各种不同的文件格式,当加载失败时返回false(你还可以调用ilGetError来查询其错误代码).若是这种情况,我们简单地抛出一个异常.如果你还记得,在第一篇文章中这些异常将会在main函数中被捕获,并且在退出程序前显示一个错误信息.接下来,我们获取图片的宽度和高度(ilGetInteger和ilCopyPixels函数对当前活动图片总是有效的).然后,我们为m_TextData.pData域分配空间:每个像素由4个字节编码(因为是RGBA类型).然后,调用ilCopyPixels函数来拷贝缓冲区中的图片数据.前三个参数分别是开始拷贝点的x,y,z位置,接下来的参数是这些方向上待拷贝的像素数目.然后指定图片格式:RGBA意味着每个颜色通道一个字节(RGB),以及alpha通道一个字节(A).Alpha通道用于指明像素的透明度,值为0表示全透明,值为255表示不透明.然后指明了每个部分的类型:它们必须以无符号字节进行编码.最后一个参数是包含像素数据的缓冲区指针.最后,由于我们不再需要DevIL图片数据,因此将其删除.
注:在OpenGL中使用DevIL加载纹理图片有更加简单的方式.ILUT库允许你调用ilutGLLoadImage函数加载图片并直接联系到一个OpenGL纹理上,此函数会返回OpenGL纹理的id.这是最简单的方式,但如此一来你就无法对原始字节数据进行操作,而这是接下来进行抠色(Color Keying)时要做的.
一旦数据从文件中加载出来后,我们就需要产生一个新的OpenGL纹理,并为之提供数据.这在纹理被首次要请求时,在CTexture::Bind函数中实现:
纹理绑定
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->voidCTexture::Bind()const
{
//IfthetexturehasnotbeengeneratedinOpenGLyet,
//generateit.
if(!m_glId)
{
//GenerateonenewtextureId.
glGenTextures(1,&m_glId);
//Makethistexturetheactiveone,sothateach
//subsequentglTex*callswillaffectit.
glBindTexture(GL_TEXTURE_2D,m_glId);
//Specifyalinearfilterforboththeminificationand
//magnification.
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
//SetsdrawingmodetoGL_MODULATE
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
//Finally,generatethetexturedatainOpenGL.
glTexImage2D(GL_TEXTURE_2D,0,4,m_TextData.nWidth,m_TextData.nHeight,
0,GL_RGBA,GL_UNSIGNED_BYTE,m_TextData.pData);
}
//MaketheexistingtexturespecifiedbyitsOpenGLid
//theactivetexture.
glBindTexture(GL_TEXTURE_2D,m_glId);
}
OpenGL重要的一点是它每次只能使用一个纹理.因此,要想对一个多边形贴纹理,就必须选中活动纹理(也叫”绑定”).这通过调用glBindTexture来完成.每个OpenGL纹理都有其id,这里我们将其存储在 CTexture类的m_glId成员变量中.id为0表明纹理还没有被OpenGL产生出来.因此,当此函数第一次被调用时,m_glId将会是0.此时我们将会调用glGenTextures来请求OpenGL产生一个id.
m_glId是mutable的,这是因为我们想让Bind函数是const的,而这个成员变量只被修改一次(当纹理被产生时对其修改).glGenTextures函数可以允许你产生多个Id(第一个参数就是要产生的Id个数),但我们只想要单个Id.然后我们调用glBindTexture:这将绑定纹理(通过其Id)到活动的2维纹理上.这是必须的,因为接下来的纹理操作将会影响到你这里指定的特定纹理.
接下来的纹理操作就不解释了,可以参考红宝书…
抠色(Color Keying)
我们总是blit矩形区域的图片,但是很显然,几乎没有一个游戏的角色图片是矩形的。美工把图片画到一个矩形范围内,如果设定了特定的背景颜色,我们就可以把矩形图片上的角色“抠”下来,相对于背景来说,我们就是把不属于角色的背景颜色扣掉,故称抠色。有些文件格式不支持透明通道(比如bmp文件),因此如果你想让纹理图片的某些部分透明,唯一的选择就是使用一个特定的颜色来欺骗玩家.OpenGL并不支持抠色,但通过纹理图片的Alpha通道可以很轻松地加入这个特性.这就是CTexture::SetColorKey函数所做的:
抠色(Color Keying)
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->voidCTexture::SetColorKey(unsignedcharRed,unsignedcharGreen,unsignedcharBlue)
{
//IfthetexturehasalreadybeenspecifiedtoOpenGL,
//wedeleteit.
if(m_glId)
{
glDeleteTextures(1,&m_glId);
m_glId=0;
}
//Forallthepixelsthatcorrespondtothespecifedcolor,
//setthealphachannelto0(transparent)andresettheother
//onesto255.
unsignedlongCount=m_TextData.nWidth*m_TextData.nHeight*4;
for(unsignedlongi=0;i<Count;i+=4)
{
if((m_TextData.pData[i]==Red)&&(m_TextData.pData[i+1]==Green)
&&(m_TextData.pData[i+2]==Blue))
m_TextData.pData[i+3]=0;//将指定颜色的像素点设置透明的
else
m_TextData.pData[i+3]=255;//其他颜色的像素点设置为不透明
}
}
它的实现很简单:遍历所有纹理数据,寻找指定颜色的像素点,将其Alpha通道设置为0,它就变得透明了.而对于其他像素点,将其Alpha通道设置为255.在这样做之前,我们必须先检查纹理是否已经指定给OpenGL了.若是,则必须在OpenGL中重新加载纹理.这只需要通过设置m_glId为0就可以完成(还记得吗?在Bind函数中会首先检查这个变量是否为0!).
最后,纹理是引用计数的,并且它的构造函数是受保护的,因此你无法直接创建一个CTexture对象.引用计数是通过下面两个函数实现的:
<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->voidCTexture::AddReference()
{
//Increasethereferencecount.
m_iRefCount++;
}
voidCTexture::ReleaseReference()
{
//Decreasethereferencecount.Ifitreaches0,
//thetextureisreleasedfromthetexturemanager.
m_iRefCount--;
if(m_iRefCount==0)
CTextureManager::GetInstance()->ReleaseTexture(m_strTextName);
}
之所以要使用引用计数,是因为多个CImage对象可以引用同一个纹理.我们必须知道此时有多少个CImage对象在使用此纹理,而不是当一个CImage对象销毁时就任意释放纹理资源.
分享到:
相关推荐
OpenGL ES Tutorial for Android – Part I – Setting up the view OpenGL ES Tutorial for Android – Part II – Building a polygon OpenGL ES Tutorial for Android – Part III – Transformations OpenGL ES ...
使用C++代码封装的win32操作类, 与MFC相似,对于学习SDK与C++是巨好的参考 Tutorials Menu of tutorials Tutorial 1: The Simplest Window Tutorial 2: Using Classes and Inheritance Tutorial 3: Using ...
张正友标定法基础上的深入理解与实现
讲解如何入门PyTorch,包括基础原理知识、numpy与PyTorch的区别以及案例研究实例
<br>Introduction <br>This tutorial will start with the very basis of File I/O (Input/Output) in C++. After that, I will look into aspects that are more advanced, showing you some tricks, and ...
从github下载的UVM入门教程。 网页版教程 http://cluelogic.com/2011/07/uvm-tutorial-for-candy-lovers-overview/ 原始地址 https://github.com/cluelogic/uvm-tutorial-for-candy-lovers
This tutorial attempts to get you started developing with the Win32 API as quickly and clearly as possible.
These tutorials will contain comprehensive coverage on Game Programming in OpenGL. These articles are going to assume some familiarty with OpenGL, and C/C++, but thats about it. Not only will these ...
A-tutorial-on-learning-with-Bayesian-networks,A-tutorial-on-learning-with-Bayesian-networksA-tutorial-on-learning-with-Bayesian-networks。
After you have created a new Win32 Application (NOT a console application) in Visual C++, you will need to link the OpenGL libraries. In Visual C++ go to Project, Settings, and then click on the LINK ...
iBATIS-SqlMaps-2-Tutorial_cniBATIS-SqlMaps-2-Tutorial_cn.pdf.pdfiBATIS-SqlMaps-2-Tutorial_cn.pdfiBATIS-SqlMaps-2-Tutorial_cn.pdf
A Tutorial on Reed-Solomon Coding for Fault-Tolerance in RAID-like Systems
forgers win32 asm tutorial
A-Tutorial-on-Joint-Radar-and-Communication-Transmission-for-Veh
https://www.pyimagesearch.com/2018/07/19/opencv-tutorial-a-guide-to-learn-opencv/ 代码
The C++ Standard Library: A Tutorial and Reference, Second Edition, describes this library as now incorporated into the new ANSI/ISO C++ language standard (C++11). The book provides comprehensive ...
Designed as a guidebook for those who want to become a Java developer, Java 7: A Comprehensive Tutorial discusses the essential Java programming topics that you need to master in order teach other ...
一个基于PyOpenGL的Python opengl-tutorial。 所有内容均遵循 您还可以在这里找到C实现: : 表中的内容 tu_00_glfw_window_sample:GLFW版本彩色立方体。 原始教程=> tu_01_color_cube:GLUT版本彩色立方体。 原始...
Getting Started with C++ Audio Programming for Game Developers covers a broad range of topics – from loading and playing audio files to simulating sounds within a virtual environment and implementing...
He efficiently presents the complex C++ language in this well-designed tutorial/reference that both students and seasoned programmers will appreciate. The book is ideal as a primary text for ...