首先,像其它教程那样,我们需要包含头文件等东西。
#include <irrlicht.h> #include <iostream>
using namespace irr;
#pragma comment(lib, "Irrlicht.lib")
|
而在这个例子中,我们需要一个事件接收器,使用户能够在三种材质类型中转换。另外,这个事件接收器还会创建一些小的GUI窗口来显示当前使用的材质类型。这个类并没有特殊的实现,因此你可以不阅读代码。
class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(scene::ISceneNode* room,
gui::IGUIEnvironment* env, video::IVideoDriver* driver)
{
// store pointer to room so we can change its drawing mode
Room = room;
Driver = driver;
// set a nicer font
gui::IGUISkin* skin = env->getSkin();
gui::IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
// add window and listbox
gui::IGUIWindow* window = env->addWindow(
core::rect(490,390,630,470), false, L"Use 'E' + 'R' to change");
ListBox = env->addListBox(
core::rect(2,22,135,78), window);
ListBox->addItem(L"Diffuse");
ListBox->addItem(L"Bump mapping");
ListBox->addItem(L"Parallax mapping");
ListBox->setSelected(1);
// create problem text
ProblemText = env->addStaticText(
L"Your hardware or this renderer is not able to use the "\
L"needed shaders for this material. Using fall back materials.",
core::rect(150,20,470,60));
ProblemText->setOverrideColor(video::SColor(100,255,255,255));
// set start material (prefer parallax mapping if available)
video::IMaterialRenderer* renderer =
Driver->getMaterialRenderer(video::EMT_PARALLAX_MAP_SOLID);
if (renderer && renderer->getRenderCapability() == 0)
ListBox->setSelected(2);
// set the material which is selected in the listbox
setMaterial();
}
bool OnEvent(SEvent event)
{
// check if user presses the key 'E' or 'R'
if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
!event.KeyInput.PressedDown && Room && ListBox)
{
// change selected item in listbox
int sel = ListBox->getSelected();
if (event.KeyInput.Key == irr::KEY_KEY_R)
++sel;
else
if (event.KeyInput.Key == irr::KEY_KEY_E)
--sel;
else
return false;
if (sel > 2) sel = 0;
if (sel < 0) sel = 2;
ListBox->setSelected(sel);
// set the material which is selected in the listbox
setMaterial();
}
return false;
}
private:
// sets the material of the room mesh the the one set in the
// list box.
void setMaterial()
{
video::E_MATERIAL_TYPE type = video::EMT_SOLID;
// change material setting
switch(ListBox->getSelected())
{
case 0: type = video::EMT_SOLID;
break;
case 1: type = video::EMT_NORMAL_MAP_SOLID;
break;
case 2: type = video::EMT_PARALLAX_MAP_SOLID;
break;
}
Room->setMaterialType(type);
|
如果材质不能100%被显示,那么我们需要添加一个警告。这是没问题的,会使用低级的材质渲染,但起码告诉用户要看到更好的效果应该改换更好的硬件。我们会简单的检测在当前的硬件下,材质渲染器能否绘制出全部效果。如果是这种情况,那么调用IMaterialRenderer::getRenderCapability()会返回0。(译注:也就是说,如果硬件支持要查询的材质类型,那么返回0)
video::IMaterialRenderer* renderer = Driver->getMaterialRenderer(type);
// display some problem text when problem
if (!renderer || renderer->getRenderCapability() != 0)
ProblemText->setVisible(true);
else
ProblemText->setVisible(false);
}
private:
gui::IGUIStaticText* ProblemText;
gui::IGUIListBox* ListBox;
scene::ISceneNode* Room;
video::IVideoDriver* Driver;
};
|
现在是真正的乐趣。我们创建一个IrrLicht设置并开始设置场景。
int main()
{
// let user select driver type
video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;
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) Apfelbaum 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_SOFTWARE2;break; case 'f': driverType = video::EDT_NULL; break; default: return 0; }
// create device
IrrlichtDevice* device = createDevice(driverType, core::dimension2d(640, 480));
if (device == 0)
return 1; // could not create selected driver.
|
在开始有趣的部份之前,我们还有一些简单的事情要做:保存指针作为引擎最重要的部份 (video driver, scene manager, gui environment) ,这样就可以安全地减少打字量了,再添加一个IrrLicht引擎标志到窗口和一个受用户控制的每一人称射击(first person shooter)类型的摄像机。同时,我们设置引擎用32位模式保存全部纹理。因为视差映射(parallax mapping),所以是必需的。
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
// add irrlicht logo
env->addImage(driver->getTexture("../../media/irrlichtlogoalpha.tga"),
core::position2d(10,10));
// add camera
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0,100.0f,300.0f);
camera->setPosition(core::vector3df(-200,200,-200));
// disable mouse cursor
device->getCursorControl()->setVisible(false);
|
因为我们想令场景看起来有点神秘,于是添加了迷雾。可以调用IVideoDriver::setFog()函数来实现。使用这个函数能够设置各种迷雾。在这个例子中,要使材质工作良好,所以使用了像素迷雾(pixel fog)。请注意,你必需设置每一个场景节点的材质标志EMF_FOG_ENABLE为‘true’,使场景节点受迷雾的影响。
driver->setFog(video::SColor(0,138,125,81), true, 250, 1000, 0, true);
|
为了使显示有趣,我们从*.3ds文件加载一个网格作为房间(我使用anim8or建这个模型)。 这个房间就是specialFX例子中的那个。也许你还记得那个教程,我根本没好的模型,因此并没有用模型中的纹理贴图,而用IMeshManipulator::makePlanarTextureMapping()方法简单修复。
scene::IAnimatedMesh* roomMesh = smgr->getMesh(
"../../media/room.3ds");
scene::ISceneNode* room = 0;
if (roomMesh)
{
smgr->getMeshManipulator()->makePlanarTextureMapping(
roomMesh->getMesh(0), 0.003f);
|
现在是第一个令人兴奋的地方:如果成功加载网格,那么需要将纹理应用于它。因为我们想这个房间用一个很cool的材质显示,所以必须比普通的设置纹理多做一些。并非像通常那样加载一个彩色贴图,而是加载一个灰度图作为高度图。根据这个高度图,我们创建一个法线贴图作为房间的第二层纹理。如果你已经有一个法线贴图,可以直接设置为房间的纹理,但随便地找了一下,并无发现合适这个纹理的法线贴图。法线贴图的材质已经用VideoDriver::makeNormalMapTexture()方法产生了。第二个参数是指定高度图的高度。如果你设置一个更大的值,那么贴图看起来会更嶙峋。
video::ITexture* colorMap = driver->getTexture("../../media/rockwall.bmp");
video::ITexture* normalMap = driver->getTexture("../../media/rockwall_height.bmp");
driver->makeNormalMapTexture(normalMap, 9.0f);
|
但只设置彩色贴图和法线贴图并不足够。我们要用材质,还需要顶点的附加信息如切线(tangents)和副法线(binormals)(译注:法线,切线和副法线就能构成切线空间(tangent space))。 我们是懒得计算这些信息了,那就让IrrLicht引擎来做吧。这就是调用IMeshManipulator::createMeshWithTangents()函数的原因了。这个函数根据指定的网格创建一个带有切线和副法线的网格副本(mesh copy)。接着我们要做的是,根据这个网格副本简单地创建一个标准网格场景节点,并设置彩色贴图,法线贴图和调整其它材质属性。注意,我们设置了EMF_FOG_ENABLE为真,使房间应用迷雾效果。
scene::IMesh* tangentMesh = smgr->getMeshManipulator()->createMeshWithTangents( roomMesh->getMesh(0)); room = smgr->addMeshSceneNode(tangentMesh); room->setMaterialTexture(0, colorMap); room->setMaterialTexture(1, normalMap); room->getMaterial(0).EmissiveColor.set(0,0,0,0); room->setMaterialFlag(video::EMF_FOG_ENABLE, true); room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID); room->getMaterial(0).MaterialTypeParam = 0.02f; // adjust height for parallax effect // drop mesh because we created it with a create.. call. tangentMesh->drop(); }
|
下一步,我们根据像素光照创建房间的阴影,并用相同的材质属性创建一个球体,但设置成半透明。另外,为了使球体像我们熟悉的地球,设置它旋转。这个程序与之前的都很相似。不同的是从一个已经包含彩色贴图的*.x文件中加载一个网格,因此我们不必手动加载纹理了。但这个球体对我们来说实在太小了,所以设置它的比例为50倍。
// add earth sphere
scene::IAnimatedMesh* earthMesh = smgr->getMesh("../../media/earth.x");
if (earthMesh)
{
// create mesh copy with tangent informations from original earth.x mesh
scene::IMesh* tangentSphereMesh =
smgr->getMeshManipulator()->createMeshWithTangents(earthMesh->getMesh(0));
// set the alpha value of all vertices to 200
smgr->getMeshManipulator()->setVertexColorAlpha(tangentSphereMesh, 200);
// scale the mesh by factor 50
smgr->getMeshManipulator()->scaleMesh(
tangentSphereMesh, core::vector3df(50,50,50));
// create mesh scene node
scene::ISceneNode* sphere = smgr->addMeshSceneNode(tangentSphereMesh);
sphere->setPosition(core::vector3df(-70,130,45));
// load heightmap, create normal map from it and set it
video::ITexture* earthNormalMap = driver->getTexture("../../media/earthbump.bmp");
driver->makeNormalMapTexture(earthNormalMap, 20.0f);
sphere->setMaterialTexture(1, earthNormalMap);
// adjust material settings
sphere->setMaterialFlag(video::EMF_FOG_ENABLE, true);
sphere->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
// add rotation animator
scene::ISceneNodeAnimator* anim =
smgr->createRotationAnimator(core::vector3df(0,0.1f,0));
sphere->addAnimator(anim);
anim->drop();
// drop mesh because we created it with a create.. call.
tangentSphereMesh->drop();
}
|
要在移动的灯光里才能看到像素光照的cool效果。因此我们添加一些灯光。而只加移动灯光的效果很无趣,所以为灯光加上公告板,并为其中一个加上粒子系统。这里我们首先添加一个红色的灯光并附上公告板。
// add light 1 (nearly red)
scene::ILightSceneNode* light1 =
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(0.5f, 1.0f, 0.5f, 0.0f), 200.0f);
// add fly circle animator to light 1
scene::ISceneNodeAnimator* anim =
smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
light1->addAnimator(anim);
anim->drop();
// attach billboard to the light
scene::ISceneNode* bill =
smgr->addBillboardSceneNode(light1, core::dimension2d(60, 60));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
bill->setMaterialTexture(0, driver->getTexture("../../media/particlered.bmp"));
|
第二个灯光基本和上面的相同。不同的是,我们还为它添加一个粒子系统。而且灯光移动时,粒子系统的粒子会跟随灯光。如果你想知道更多关于如何在IrrLicht引擎中使用粒子系统,可以阅读specialFx例子。 可能你已经注意到我们只添加两个灯光,简单的原因是:这个材质的低端版本是用ps1.1和vs1.1写的,因此不允许用更多的灯光。你可以添加第三个灯光到场景,但它不会用来计算墙的阴影。当然,往后的IrrLicht引擎会使用顶点着色器和像素着色器的更高版本来实现。
// add light 2 (gray)
scene::ISceneNode* light2 =
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 200.0f);
// add fly circle animator to light 2
anim = smgr->createFlyCircleAnimator (core::vector3df(0,150,0),200.0f);
light2->addAnimator(anim);
anim->drop();
// attach billboard to light
bill = smgr->addBillboardSceneNode(light2, core::dimension2d(120, 120));
bill->setMaterialFlag(video::EMF_LIGHTING, false);
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
bill->setMaterialTexture(0, driver->getTexture("../../media/particlewhite.bmp"));
// add particle system
scene::IParticleSystemSceneNode* ps =
smgr->addParticleSystemSceneNode(false, light2);
ps->setParticleSize(core::dimension2d(30.0f, 40.0f));
// create and set emitter
scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d(-3,0,-3,3,1,3),
core::vector3df(0.0f,0.03f,0.0f),
80,100,
video::SColor(0,255,255,255), video::SColor(0,255,255,255),
400,1100);
ps->setEmitter(em);
em->drop();
// create and set affector
scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
ps->addAffector(paf);
paf->drop();
// adjust some material settings
ps->setMaterialFlag(video::EMF_LIGHTING, false);
ps->setMaterialTexture(0, driver->getTexture("../../media/fireball.bmp"));
ps->setMaterialType(video::EMT_TRANSPARENT_VERTEX_ALPHA);
MyEventReceiver receiver(room, env, driver);
device->setEventReceiver(&receiver);
|
最后,绘制全部。就是这样了。
int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, 0);
smgr->drawAll();
env->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Per pixel lighting example - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
|
|