irrlicht之16.Quake3MapShader
这个例子告诉我们如何使用引擎读取一个Quake3形式的压缩地图,如何优化场景节点的渲染,以及如何创建一个用户控制摄象机。
/*
这个例子告诉我们如何使用引擎读取一个Quake3形式的压缩地图,如何优化场景节点的渲染,以及如何创建一个用户控制摄象机。
(译者注:此段注释怀疑作者并未认真处理,仅是从第二个例子简单复制粘贴过来,所以,本文中真正的独到之处:截屏技术 以及 各场景Shader的实现 却未在此说明)
首先,象其他例子一样,我们包含Irr头文件,并要求用户创建了一个设备。
*/
#include <irrlicht.h>
#include <iostream>
/*
类似于写HelloWorld例子之前需要做的准备一样,在Irrlicht引擎中,一切
函数,类命名都是在irr命名空间内的。我们依旧要告诉编辑器我们现在使用
的函数应当在irr命名空间内寻找。它有五个子命名空间,Core,Scene,Video,
Io,Gui.与HelloWorld不同的是,我们这里没有为五个子空间分别指定命名空间
的通知,因为这样做的话,在下面的代码中,你将更容易获知每个函数到底是属于
哪个命名空间内的。当然,你也可以加上using namespace XX;尽随你意了。
*/
using namespace irr;
using namespace scene;
/*
同样,为了可以使用Irrlicht.DLL文件,我们需要链接一个Irrlicht.lib文件,我们需要
进行项目设置,或者在代码中进行一次链接声明。
*/
#pragma comment(lib, "Irrlicht.lib")
// 创建一个截屏类
class CScreenShotFactory : public IEventReceiver
{
public:
CScreenShotFactory( IrrlichtDevice *device, const c8 * templateName )
{
// 存储设备指针,之后我需要用到它。
Device = device;
// 从0开始编号
Number = 0;
Filename.reserve ( 256 );
FilenameTemplate = templateName;
}
bool OnEvent(SEvent event)
{
// 如果用户按下F9键,则截屏并保存为jpg文件
if (event.EventType == EET_KEY_INPUT_EVENT &&
event.KeyInput.Key == KEY_F9 &&
event.KeyInput.PressedDown == false)
{
video::IImage* image = Device->getVideoDriver()->createScreenShot();
if (image)
{
sprintf ( (c8*) Filename.c_str() ,
"%s_shot%04d.jpg",
FilenameTemplate.c_str (),
Number++
);
Device->getVideoDriver()->writeImageToFile(image, Filename.c_str(), 85 );
image->drop();
}
}
return false;
}
private:
IrrlichtDevice *Device;
u32 Number;
core::stringc Filename;
core::stringc FilenameTemplate;
};
/*
OK,开始
*/
int IRRCALLCONV main(int argc, char* argv[])
{
/*
类似HelloWorld例子,我们通过CreateDevice创建一个Irr设备,不过在这里我们允许
用户进行硬件加速设备的选择。其中软件模拟进行一次巨大的Q3场景的加载将会相当慢,
不过为了进行演示有这样一个功能,我们也把它列做选项了。
*/
// 让用户选择设备类型
video::E_DRIVER_TYPE driverType;
printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
" (d) Software Renderer\n (e) Burning's Software Renderer\n"\
" (f) NullDevice\n (otherKey) exit\n\n");
char i;
std::cin >> i;
switch(i)
{
case 'a': driverType = video::EDT_DIRECT3D9;break;
case 'b': driverType = video::EDT_DIRECT3D8;break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
case 'f': driverType = video::EDT_NULL; break;
default: return 1;
}
// 创建设备
core::dimension2di videoDim ( 800,600 );
IrrlichtDevice *device = createDevice(driverType, videoDim, 32, false );
if (device == 0)
return 1;
/*
获取一个视频驱动和场景管理的指针。
*/
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
// 设置我们的资源目录工作夹
device->getFileSystem()->addFolderFileArchive("../../media/");
/*
为了显示QUAKE3的地图,我们首先需要读取它。
Quake3地图被打包在.pk3文件中,所以我们的文件系统需要加载.pk3包文件,
在我们加载它之后,我们还需要从包文件中对其进行读取。
*/
device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
/*
现在我们可以通过调用getMesh()函数来进行Mesh的读取。我们获得了一个动画Mesh
IAnimatedMesh的指针。然而我们可能有疑问,Quake3地图并非一个动画,我们为什么要
使用IAnimatedMesh动画Mesh呢?我们先研究下Quake3的地图,它是由一个巨大的模型
以及一些贴图文件组成的。我们可以理解为,它是由一个动画组成,而这个动画仅有一桢,
所以我们获得动画的第一桢getMesh(0)(其中0就是指定桢数),然后使用它创建一个八叉树场景节点。
八叉树的作用是对场景渲染进行优化,就是仅仅渲染摄象机所见的场景,这个请自行
查看3D渲染相关书籍。
相对于八叉树场景节点的另一种加载方式就是直接创建一个AnimatedMeshSceneNode,
动画Mesh场景节点,但是这样做的话就不会进行优化的拣选,它会一次性加载绘制所有的场景。
在下面的代码里,我两种类型都写了,你可以切换着进行尝试一下。
值得注意的是八叉树场景的适用范围一般是大型的室外场景加载。
*/
scene::IQ3LevelMesh* mesh = (scene::IQ3LevelMesh*) smgr->getMesh("maps/20kdm2.bsp");
// 为了能够截图,我们设置一个事件接收器
CScreenShotFactory screenshotFactory ( device, "20kdm2" );
device->setEventReceiver ( &screenshotFactory );
/*
更改Mesh的结构类型,以获取更快的渲染速度
(译者注;此处作者原文意为创建一个几何体以获取更快的速度,这明显是错误的,
跟踪带源码中可获知,getMesh()中的参数是说明需要获取Mesh类型,而从一个Mesh中
重新获取本Mesh,而修改了Mesh结构类型,必然进行的是优化结构的操作,并无创建工作)
*/
scene::ISceneNode* node = 0;
if ( mesh )
{
scene::IMesh *geometry = mesh->getMesh(quake3::E_Q3_MESH_GEOMETRY );
//node = smgr->addOctTreeSceneNode(geometry, 0, -1, 128);
node = smgr->addMeshSceneNode ( geometry );
}
/*
现在,为每个场景节点创建自己的Shader。这些Shaders目标存储在QuakeMesh场景quake3::E_Q3_MESH_ITEMS中,
Shaders的ID存储在材质参数中。
(译者注:此处就是与Irr第二个例子不同之处,它将场景中的MeshItems单独进行了渲染,这些MeshItems在本例中
表现为各个灯盏以及上面的火焰等一些附属物件)
*/
if ( mesh )
{
// 这些额外的Mesh会非常庞大
scene::IMesh * additional_mesh = mesh->getMesh ( quake3::E_Q3_MESH_ITEMS );
for ( u32 i = 0; i!= additional_mesh->getMeshBufferCount (); ++i )
{
IMeshBuffer *meshBuffer = additional_mesh->getMeshBuffer ( i );
const video::SMaterial &material = meshBuffer->getMaterial();
// Shaders索引值保存在材质参数中
s32 shaderIndex = (s32) material.MaterialTypeParam2;
// 普通的附加Mesh可以不需要额外支持的进行渲染,但是火焰Shader不行,它需要特殊支持
const quake3::SShader *shader = mesh->getShader ( shaderIndex );
if ( 0 == shader )
{
continue;
}
//core::stringc szTemp = NULL;
//quake3::dumpShader ( szTemp, shader );
// 通过管理器对每个MeshBuffer提供一个正确的Shader,将Shader绑定入场景Mesh
smgr->addQuake3SceneNode ( meshBuffer, shader );
}
// 原始的Mesh数据已经不需要了
mesh->releaseMesh ( quake3::E_Q3_MESH_ITEMS );
}
/*
现在我们仅仅需要一个摄象机去观察这张地图。这次我们设计一个可用户控制的
灵活摄象机。在Irr引擎中有许多不同类型的摄象机:例如,Maya摄象机就类似于
Maya软件中的摄象机控制,左键按下可进行旋转,两键按下就可以进行缩放,
右键按下就可以进行移动。假如我们想创建这样操作方式的摄象机,那么只要
addCameraSceneNodeMaya()就可以了。而我们现在需要设计的摄象机则是类似于
标准FPS的控制设定,所以我们调用addCameraSceneNodeFPS()函数来创建。
*/
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
/*
我们还需要一个不错的初始观察点,实际上,这些点在Quake3地图中是定义好了的,在配置中这些点的索引文字是
“info_player_deathmatch”,我们找到这些点之后,随机选择一个点做为摄象机初始点。
*/
if ( mesh )
{
const quake3::tQ3EntityList &entityList = mesh->getEntityList ();
quake3::SEntity search;
search.name = "info_player_deathmatch";
s32 index = entityList.binary_search_const ( search );
if ( index >= 0 )
{
const quake3::SVarGroup *group;
s32 notEndList;
do
{
group = entityList[ index ].getGroup(1);
u32 parsepos = 0;
core::vector3df pos = quake3::getAsVector3df ( group->get ( "origin" ), parsepos );
parsepos = 0;
f32 angle = quake3::getAsFloat ( group->get ( "angle"), parsepos );
core::vector3df target ( 0.f, 0.f, 1.f );
target.rotateXZBy ( angle, core::vector3df () );
camera->setPosition ( pos );
camera->setTarget ( pos + target );
index += 1;
notEndList = ( index < (s32) entityList.size () &&
entityList[index].name == search.name &&
(device->getTimer()->getRealTime() >> 3 ) & 1
);
} while ( notEndList );
}
}
/*
屏蔽鼠标图标
*/
device->getCursorControl()->setVisible(false);
// 读取一个Irr引擎LOGO
gui::IGUIEnvironment* env = device->getGUIEnvironment();
env->addImage(driver->getTexture("irrlichtlogo2.png"),core::position2d<s32>(10, 10));
// 根据设备不同添加不同的设备Logo
core::position2di pos ( videoDim.Width - 128, videoDim.Height - 64 );
switch ( driverType )
{
case video::EDT_BURNINGSVIDEO:
env->addImage(driver->getTexture("burninglogo.png"),pos );
break;
case video::EDT_OPENGL:
env->addImage(driver->getTexture("opengllogo.png"),pos );
break;
case video::EDT_DIRECT3D8:
case video::EDT_DIRECT3D9:
env->addImage(driver->getTexture("directxlogo.png"),pos );
break;
}
/*
我们做完了所有的事情,现在我们开始绘制它吧。我们还需要在窗口的标题上
显示当前的FPS。
if (device->isWindowActive()) 这一行代码是可选的,但是为了预防由于
切换活动窗口而导致引擎渲染桢速率显示不正确,还是加上吧。
*/
int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255,20,20,40));
smgr->drawAll();
env->drawAll();
driver->endScene();
int fps = driver->getFPS();
//if (lastFPS != fps)
{
io::IAttributes * attr = smgr->getParameters();
s32 calls = attr->getAttributeAsInt ( "calls" );
s32 culled = attr->getAttributeAsInt ( "culled" );
core::stringw str = L"Q3 [";
str += driver->getName();
str += "] FPS:";
str += fps;
str += " Cull:";
str += calls;
str += "/";
str += culled;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
/*
最后,删除渲染设备
*/
device->drop();
return 0;
}