官方代码($sdk)\examples\16.Quake3MapShader
这个例子很像第2个官方例子,使用的场景模型也跟那例子用的。唯一的区别是,这个例子中增加了对Quake3压缩地图的场景进行更高级点的渲染,使它看起来比第2个例子更好。下面看具体代码。
#include <irrlicht.h>
#include "driverChoice.h"
//下面的宏用来定义Quake3允许载入的级别
#define IRRLICHT_QUAKE3_ARENA
//#define ORIGINAL_QUAKE3_ARENA
//#define CUSTOM_QUAKE3_ARENA
//#define SHOW_SHADER_NAME
//共有2个级别,例子里默认使用的是IRRLICHT_QUAKE3_ARENA,它是irr的Quake3渲染级别。另外一个是Quake3竞技场原始效果级别ORIGINAL_QUAKE3_ARENA。使用Quake3竞技场原始效果级别时,可以开启用户自定义效果CUSTOM_QUAKE3_ARENA宏。通过注释和取消注释来编译,可以看到不同级别的渲染效果。SHOW_SHADER_NAME宏用来开启关闭显示场景中用到的quake3 shader的名字。
//如果定义了ORIGINAL_QUAKE3_ARENA
#ifdef ORIGINAL_QUAKE3_ARENA
//定义QUAKE3_STORAGE_FORMAT宏
#define QUAKE3_STORAGE_FORMAT addFolderFileArchive
//定义QUAKE3_STORAGE_1宏
#define QUAKE3_STORAGE_1 "/baseq3/"
//如果定义了CUSTOM_QUAKE3_ARENA
#ifdef CUSTOM_QUAKE3_ARENA
//定义QUAKE3_STORAGE_2宏
#define QUAKE3_STORAGE_2 "/cf/"
//定义QUAKE3_MAP_NAME宏
#define QUAKE3_MAP_NAME "maps/cf.bsp"
#else
//定义QUAKE3_MAP_NAME宏
#define QUAKE3_MAP_NAME "maps/q3dm8.bsp"
#endif
#endif
//如果定义了IRRLICHT_QUAKE3_ARENA宏
#ifdef IRRLICHT_QUAKE3_ARENA
//定义QUAKE3_STORAGE_FORMAT宏
#define QUAKE3_STORAGE_FORMAT addFileArchive
//定义QUAKE3_STORAGE_1宏
#define QUAKE3_STORAGE_1 "../../media/map-20kdm2.pk3"
//定义QUAKE3_MAP_NAME宏
#define QUAKE3_MAP_NAME "maps/20kdm2.bsp"
#endif
using namespace irr;
using namespace scene;
#ifdef _MSC_VER
#pragma comment(lib,"Irrlicht.lib")
#endif
//截屏类。这个类继承IEventReceiver事件接收接口,用来检查用户操作按键情况,在按下F9键时进行屏幕截屏。
class CScreenShotFactory : publicIEventReceiver
{
public:
//device irr设备、templateName截屏临时名字、node场景节点(本例中没用到)
CScreenShotFactory( IrrlichtDevice *device, const c8 * templateName,ISceneNode* node )
: Device(device), Number(0), FilenameTemplate(templateName), Node(node)
{
FilenameTemplate.replace ( '/', '_' );
FilenameTemplate.replace ( '\\', '_' );
}
bool OnEvent(const SEvent& event)
{
//检查是否是键盘按下事件
if ((event.EventType == EET_KEY_INPUT_EVENT) && event.KeyInput.PressedDown)
{
//F9键按下,截屏并保存为jpg文件
if (event.KeyInput.Key == KEY_F9)
{
//创建截屏图像
video::IImage* image =Device->getVideoDriver()->createScreenShot();
if (image)
{
//创建文件名
c8 buf[256];
snprintf(buf, 256,"%s_shot%04d.jpg",FilenameTemplate.c_str(),++Number);
//将截屏图像写入文件
Device->getVideoDriver()->writeImageToFile(image, buf, 85 );
image->drop();
}
}
//按下F8键,打开或关闭Debug数据显示
else if (event.KeyInput.Key == KEY_F8)
{
if(Node->isDebugDataVisible())
Node->setDebugDataVisible(scene::EDS_OFF);
else
Node->setDebugDataVisible(scene::EDS_BBOX_ALL);
}
}
return false;
}
private:
IrrlichtDevice *Device;
u32Number;
core::stringc FilenameTemplate;
ISceneNode* Node;
};
int IRRCALLCONV main(int argc, char* argv[])
{
/*类似HelloWorld例子,通过CreateDevice创建一个Irr设备,不过在这里允许用户进行硬件加速设备的选择。其中软件加速进行巨大的Q3场景的显示将会相当慢,本例子为了进行演示有这样一个功能,也把它列做选项了。*/
//选择使用的irr设备
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if(driverType==video::EDT_COUNT)
return 1;
//创建irr设备,失败则退出程序
const core::dimension2du videoDim(800,600);
IrrlichtDevice *device = createDevice(driverType, videoDim, 32, false );
if(device == 0)
return 1;
//如果有命令行参数提供的地图文件名,就用它做场景。否则就用默认的QUAKE3_MAP_NAME文件名。
const char* mapname=0;
if(argc>2)
mapname = argv[2];
else
mapname = QUAKE3_MAP_NAME;
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
//添加自己的私有资源文件工作目录
device->getFileSystem()->addFileArchive("../../media/");
//为了显示QUAKE3的地图,首先需要读取它。Quake3地图被打包在.pk3文件中,所以irr的文件系统需要加载.pk3包文件,在加载它之后,还需要从包文件中对其进行读取。
//这里QUAKE3_STORAGE_FORMAT宏,根据前面的显示级别定义,被设成了函数名addFolderFileArchive或addFileArchive。
//根据有无命令行参数设置地图工作文件夹或压缩包
if(argc>2)
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(argv[1]);
else
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(QUAKE3_STORAGE_1);
//如果设置了QUAKE3_STORAGE_2宏,就加载相关的文件(对QUAKE3地图格式为也不清楚,从代码只能知道,加载了QUAKE3_STORAGE_2级别的模型,画质会提高,但对渲染的要求也有提高)
#ifdef QUAKE3_STORAGE_2
device->getFileSystem()->QUAKE3_STORAGE_FORMAT(QUAKE3_STORAGE_2);
#endif
//设置允许Quake3着色器控制ZWRITE
smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT,true);
/*现在可以通过调用getMesh()函数来进行Mesh的读取。我们获得了一个动画Mesh的IAnimatedMesh的指针。Quake3地图并非一个动画,这里为什么要使用IAnimatedMesh动画Mesh呢? Quake3的地图是由一个巨大的模型以及一些贴图文件组成的。我们可以理解为,它是由一个动画组成,而这个动画仅有一桢,所以获得动画的第一桢getMesh(0)(其中0就是指定桢数),然后使用它创建一个八叉树场景节点。八叉树的作用是对场景渲染进行优化,就是仅仅渲染摄象机所见的场景。相对于八叉树场景节点的另一种加载方式就是直接创建一个AnimatedMeshSceneNode,动画Mesh场景节点,但是这样做的话就不会进行优化的拣选,它会一次性加载绘制所有的场景。值得注意的是八叉树场景的适用范围一般是大型的室外场景加载*/
scene::IQ3LevelMesh* const mesh =(scene::IQ3LevelMesh*)smgr->getMesh(mapname);
//更改Mesh的结构类型,以获取更快的渲染速度。添加几何网格到场景。几何网格是被优化过的,能有更快的绘制速度。
scene::ISceneNode* node = 0;
if(mesh)
{
scene::IMesh * const geometry = mesh->getMesh(quake3::E_Q3_MESH_GEOMETRY);
node = smgr->addOctreeSceneNode(geometry, 0, -1, 4096);
}
//创建一个事件接收类用来截图
CScreenShotFactory screenshotFactory(device, mapname, node);
device->setEventReceiver(&screenshotFactory);
//现在,为每个场景节点创建自己的Shader。这些Shaders目标存储在QuakeMesh场景quake3::E_Q3_MESH_ITEMS中,Shaders的ID存储在材质参数中。大多数看起来很暗,像骨头、移动的岩浆和闪烁的烛光。
if( mesh )
{
//这些额外的Mesh会非常庞大
const scene::IMesh * const additional_mesh =mesh->getMesh(quake3::E_Q3_MESH_ITEMS);
//如果定义了SHOW_SHADER_NAME宏,设置字体
#ifdef SHOW_SHADER_NAME
gui::IGUIFont *font =device->getGUIEnvironment()->getFont("../../media/fontlucida.png");
#endif
u32 count = 0;
for ( u32 i = 0; i!= additional_mesh->getMeshBufferCount(); ++i )
{
const IMeshBuffer* meshBuffer = additional_mesh->getMeshBuffer(i);
const video::SMaterial& material = meshBuffer->getMaterial();
//Shaders索引值保存在材质参数中
const s32 shaderIndex = (s32) material.MaterialTypeParam2;
//普通的附加Mesh可以不需要额外支持的进行渲染,但是火焰Shader不行,它需要特殊支持
const quake3::IShader *shader = mesh->getShader(shaderIndex);
if (0 == shader)
{
continue;
}
//通过管理器对每个MeshBuffer提供一个正确的Shader,将Shader绑定入场景Mesh
node = smgr->addQuake3SceneNode(meshBuffer, shader);
//如果定义了SHOW_SHADER_NAME宏,为场景节点添加一个文章告示板显示shader名。
#ifdef SHOW_SHADER_NAME
count += 1;
core::stringw name( node->getName() );
node = smgr->addBillboardTextSceneNode(font, name.c_str(),node,core::dimension2d<f32>(80.0f, 8.0f),core::vector3df(0, 10, 0));
#endif
}
}
/*现在仅仅需要一个摄象机去观察这张地图。这次设计一个可以用户控制的灵活摄象机。在Irr引擎中有许多不同类型的摄象机:例如,Maya摄象机就类似于Maya软件中的摄象机控制,左键按下可进行旋转,两键按下就可以进行缩放,右键按下就可以进行移动。假如想创建这样操作方式的摄象机,那么只要addCameraSceneNodeMaya()就可以了。而现在需要设计的摄象机则是类似于标准FPS的控制设定,所以调用addCameraSceneNodeFPS()函数来创建。*/
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
/*还需要一个不错的初始观察点,实际上,这些点在Quake3地图中是定义好了的,在配置中这些点的索引文字是“info_player_deathmatch”,找到这些点之后,随机选择一个点做为摄象机初始点。*/
if( mesh )
{
quake3::tQ3EntityList &entityList = mesh->getEntityList();
quake3::IEntity search;
search.name ="info_player_deathmatch";
s32 index = entityList.binary_search(search);
if (index >= 0)
{
s32 notEndList;
do
{
const quake3::SVarGroup *group= entityList[index].getGroup(1);
u32 parsepos = 0;
const core::vector3df pos=quake3::getAsVector3df(group->get("origin"), parsepos);
parsepos = 0;
const f32 angle =quake3::getAsFloat(group->get("angle"), parsepos);
core::vector3df target(0.f,0.f, 1.f);
target.rotateXZBy(angle);
camera->setPosition(pos);
camera->setTarget(pos +target);
++index;
/*notEndList = (index <(s32) entityList.size () && entityList[index].name == search.name&& (device->getTimer()->getRealTime() >> 3 ) & 1 );*/
notEndList = index == 2;
} while ( notEndList );
}
}
//屏蔽鼠标图标
device->getCursorControl()->setVisible(false);
//加载一个Irr引擎LOGO
gui->addImage(driver->getTexture("irrlichtlogo2.png"),core::position2d<s32>(10,10));
//根据设备不同添加不同的设备Logo
const core::position2di pos(videoDim.Width - 128, videoDim.Height - 64);
switch ( driverType )
{
case video::EDT_BURNINGSVIDEO:
gui->addImage(driver->getTexture("burninglogo.png"),pos);
break;
case video::EDT_OPENGL:
gui->addImage(driver->getTexture("opengllogo.png"),pos);
break;
case video::EDT_DIRECT3D8:
case video::EDT_DIRECT3D9:
gui->addImage(driver->getTexture("directxlogo.png"), pos);
break;
}
/*做完了所有的事情,现在开始绘制它。还需要在窗口的标题上显示当前的FPS。if (device->isWindowActive()) 这一行代码是可选的,但是为了预防由于切换活动窗口而导致引擎渲染桢速率显示不正确,还是加上吧。*/
intlastFPS = -1;
while(device->run())
if(device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255,20,20,40));
smgr->drawAll();
gui->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
io::IAttributes * const attr = smgr->getParameters();
core::stringw str = L"Q3 [";
str += driver->getName();
str += "] FPS:";
str += fps;
#ifdef _IRR_SCENEMANAGER_DEBUG
str += " Cull:";
str +=attr->getAttributeAsInt("calls");
str += "/";
str +=attr->getAttributeAsInt("culled");
str += " Draw: ";
str +=attr->getAttributeAsInt("drawn_solid");
str += "/";
str +=attr->getAttributeAsInt("drawn_transparent");
str += "/";
str +=attr->getAttributeAsInt("drawn_transparent_effect");
#endif
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
//最后,删除渲染设备
device->drop();
return 0;
}
这个例子中,使用了很多quake3专有的东西,在引擎中这部分增加了quake3名字空间。对其它格式的场景模型不适用。我找了很多工具,专门的quake3建模工具没找到,只找到常见的3d max和maya的quake3模型导出插件。没法弄清到底是哪些东西导出后,能成为这例子中用得到的东西,只好不详细研究这例子了。没搞游戏的专业人员指点,只靠自己网上搜资料,好多东西要弄清楚是有困难的。