irrwowview

Tutorial 11: Per Pixel Lighting

Tutorial 11: Per Pixel Lighting
本教程演示了如何在IrrLicht引擎中使用更复杂的材质:像素光照(Per pixel lighted)表面使用了法线贴图(normal maps)和视差映射(parallax mapping)。还演示了如何使用迷雾和移动粒子系统。不必担心:在IrrLicht引擎中使用这些材质,你不需要任何着色器的经验。

本程序的效果图如下所示:

 

Lets start!

首先,像其它教程那样,我们需要包含头文件等东西。

#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.1vs1.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;

 

}

 

 

 

posted on 2008-07-04 11:24 shjy 阅读(458) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理