16 Quake3MapShader
官方代碼($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[])
{
//選擇使用的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);
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
}
}
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
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 == 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;
}
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模型導出插件。沒法弄清到底是哪些東西導出後,能成為這例子中用得到的東西,隻好不詳細研究這例子了。沒搞遊戲的專業人員指點,隻靠自己網上搜資料,好多東西要弄清楚是有困難的。