Irrlicht学习备忘录——16 Quake3MapShader

16 Quake3MapShader

    官方代码($sdk)\examples\16.Quake3MapShader

Irrlicht学习备忘录——16 Quake3MapShader_第1张图片

    这个例子很像第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模型导出插件。没法弄清到底是哪些东西导出后,能成为这例子中用得到的东西,只好不详细研究这例子了。没搞游戏的专业人员指点,只靠自己网上搜资料,好多东西要弄清楚是有困难的。


你可能感兴趣的:(学习,游戏引擎,游戏编程,Irrlicht,3D编程)