8. SpecialFX

Posted 2007/09/15 19:24 by 수달

--------------------------------------------------------------------------

튜토리얼 8 : 특수 효과

--------------------

이번 튜토리얼은 특수 효과에 대해 보인다. 스텐실버퍼 쉐도우 사용법, 파티클 시스템, 빌보드, 다이나믹 라이트 그리고 물 표면 씬 노드를 이야기 한다.

이전의 예제들과 마찬가지 시작을 할 것이나, createDevice()에 'shadows' 플래그를 true로 놔야한다. 이렇게 해야 애니메이션 하는 캐릭터에 다이나믹 쉐도우가 먹을 것이다. 만약 예제 실행이 느리다면, 이 플래그를 false로 해야한다. IRRLICHT 엔진은 해당 하드웨어가 스텐실버퍼를 지원하는지 안 하는지 확인해 쉐도우를 끌 것이다. 그러나 예제에서 원한다면 느리더라도 돌려 볼 수는 있다. (역자 주 : 다이나믹 라이트나 쉐도우는 광원의 위치에 따른 빛과 그림자 변화를 적용할지 안 할지를 결정하겠다는 말이다. '실시간 적용'이란 의미가 강하다.)


#include <irrlicht.h>
#include <iostream>

using namespace irr;

#pragma comment(lib, "Irrlicht.lib")

int main()
{
// ask if user would like shadows

char i;
printf("Please press 'y' if you want to use realtime shadows.\n");

std::cin >> i;
bool shadows = (i == 'y');

// ask user for driver

video::E_DRIVER_TYPE driverType;

printf("Please select the driver you want for this example:\n"\
" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.2\n"\
" (d) Software Renderer\n (e) NullDevice\n (otherKey) exit\n\n");

std::cin >> i;

switch(i)
{
case 'a': driverType = video::EDT_DIRECTX9; break;
case 'b': driverType = video::EDT_DIRECTX8; break;
case 'c': driverType = video::EDT_OPENGL; break;
case 'd': driverType = video::EDT_SOFTWARE; break;
case 'e': driverType = video::EDT_NULL; break;
default: return 1;
}

// create device and exit if creation failed

IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<s32>(640, 480),
16, false, shadows);

if (device == 0)
return 1; // could not create selected driver.

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();


예제는 *.3ds 파일을 읽었다. 그것은 Anim8Or(역자 주 : 3D 애니메이션 모델링 프로그램, 써 보진 않음. 확인 할 예정.)로 만든 작은 방으로 *.3ds로 익스포트 한 것이다. (이 예제를 쓸 당시 아직 IRRLICHT 엔진은 *.an8을 지원하지 않았음) 전문 아티스트가 아니기에 텍스처 맵핑이나 모델링이 엉망일 것이다. 허나 프로그램으로 이것을 보기 좋게 극복했다. 만약 최초의 모델을 보고 싶다면 아래 라인을 주석처리해 보아라. 최초의 모델과 프로그램 기법으로 처리한 것에 차이를 느낄 것이다. 또한 Anim8Or에서 재질에 설정한 방출광(역자 주 : emissive : 조명과 관계없이 Ambient, Diffuse, Specular에 영향을 미치는 광원 요소)을 끌 것이다. 이는 예제를 쓴 저자가 싫어하기 때문이다.


scene::IAnimatedMesh* mesh =

smgr->getMesh("../../media/room.3ds");

smgr->getMeshManipulator()->makePlanarTextureMapping(
mesh->getMesh(0), 0.004f);

scene::ISceneNode* node = 0;

node = smgr->addAnimatedMeshSceneNode(mesh);
node->setMaterialTexture(0, driver->getTexture("../../media/wall.jpg"));
node->getMaterial(0).EmissiveColor.set(0,0,0,0);

첫번 째 특수 효과인 움직이는 물 효과를 만들어 보자. WaterSurfaceSceneNode는 매시를 받아 들인 후 그 표면을 물결치듯 만드는 씬 노드다. 만약 이 씬 노드를 EMT_REFLECTION_2_LAYER 같은 멋진 재질에 적용하면 그럴 듯한 모양이 나온다. 이것을 단지 몇 줄의 코딩으로 해낼 수 있다. 물 표면 씬 노드에 입력하는 메시로 여기서는 hill plane mesh를 사용하는데, 어떤 것이든 상관 없다. 원한다면 여기서 사용되는 room.3ds를 써도 된다. (단 매우 이상하게 보일 것이다.)


// add animated water

mesh = smgr->addHillPlaneMesh("myHill",
core::dimension2d<f32>(20,20),
core::dimension2d<s32>(40,40), 0, 0,
core::dimension2d<f32>(0,0),
core::dimension2d<f32>(10,10));

node =

smgr->addWaterSurfaceSceneNode(mesh->getMesh(0),

3.0f, 300.0f, 30.0f);
node->setPosition(core::vector3df(0,7,0));

node->setMaterialTexture(0, driver->getTexture("../../media/water.jpg"));
node->setMaterialTexture(1, driver->getTexture("../../media/stones.jpg"));

node->setMaterialType(video::EMT_REFLECTION_2_LAYER);


두번째 효과는 단순한 것으로 이미 다른 IRRLICHT 엔진 데모에서 보인 바 있다. 그것은 다이나믹 라이트와 연결된 움직이는 빌보드 이다. 쉽게 광원 씬 노드를 만든 후, 주변을 둥글게 돌게 하자. (fly around) 그 씬 노드를 빌보드 씬 노드에 붙이면 된다.


// create light

node = smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(1.0f, 0.6f, 0.7f, 1.0f), 600.0f);
scene::ISceneNodeAnimator* anim = 0;
anim = smgr->createFlyCircleAnimator (core::vector3df(0,150,0),250.0f);
node->addAnimator(anim);
anim->drop();

// attach billboard to light

node =

smgr->addBillboardSceneNode(node, core::dimension2d<f32>(50, 50));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
node->setMaterialTexture(0,

driver->getTexture("../../media/particlewhite.bmp"));


다음의 특수 효과는 매우 흥미로운 것으로 파티클 시스템이다. IRRLICHT에서 파티클 시스템은 모듈화 되어 있고 확장성 있지만 아직 사용하기엔 쉽지 않다. 엔진에는 파티클 씨앗(particle emitter : 역자 주 : 파티클 생성을 위한 기본 설정으로 '파티클 발현자', '파티클 생성자'라고 할 수 있을 것 같으나, 추상적으로 '파티클 씨앗'이란 단어를 썼다.)을 담아 무(無)에서 파티클을 만들어내는 파티클 시스템 씬 노드(scene::IParticleSystemSceneNode)가 있다. 이 씨앗은 매우 융통성 있고 만들고자 하는 파티클의 색상, 크기, 방향 등 여러 인자를 갖고 있다. 여러 종의 파티클 씨앗 있는데, 예를 들어 고정된 한 지점에서 여러 파티클이 솟아나는 것도 있다. 엔진에서 제공하는 파티클 씨앗이 충분하지 않다면, 개발자는 IParticleEmitter로 부터 상속된 자신만의 클래스를 쉽게 만들어 낼 수 있을 것이다. 이 클래스는 단지 setEmitter()를 통해 엔진에서 사용할 수 있다.


이번 예제에선 박스형 파티클 씨앗(box particle emitter)를 이용할 것으로, 박스 안에서 무작위의 파티클이 생성되는 형태이다. 각 인자들은 박스를 정의하고, 파티클의 방향, 초당 생성되는 새 파티클의 최대 최소, 색깔, 그리고 각 파티클들의 최대 최소 생명력을 정할 것이다.

단순히 씨앗들만 사용해 파티클을 만드는 것은 조금 지루할 수 있기 때문에, 그것을 보완하는 파티클 변환자(particle affector)를 이용해 파티클을 공중에 날리게 하자. 변환자는 파티클 시스템에 추가될 수 있고, 중력이나 바람과 같은 추가 효과를 비슷하게 낼 수 있다. 예제에서 사용할 파티클 변환자는 파티클의 색상을 바꾸는 변환자이다. 그것은 파티클을 점차 히미하게 사라지게 한다.(fade out) 파티클 씨앗과 마찬가지로 파티클 변환자는 개발자에 의해 더 좋게 만들어 질 수 있다. 즉, IParticleAffector로부터 상속한 class를 addAffector()에 넣어주기만 하면 된다.

지금까지 만든 파티클 시스템에 적당한 재질을 설정하면 그럴싸한 캠프 파이어를 구경 할 수 있을 것이다. 또한 재질, 텍스처, 파티클 씨앗, 파티클 변환자 등 인자를 변환해 보면, 쉽게 연막, 비, 폭발, 눈(雪) 등을 만들 수 있다.


// create a particle system

scene::IParticleSystemSceneNode* ps = 0;
ps = smgr->addParticleSystemSceneNode(false);
ps->setPosition(core::vector3df(-70,60,40));
ps->setScale(core::vector3df(2,2,2));

ps->setParticleSize(core::dimension2d<f32>(20.0f, 20.0f));

scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d<f32>(-7,0,-7,7,1,7),
core::vector3df(0.0f,0.06f,0.0f),
80,100,
video::SColor(0,255,255,255), video::SColor(0,255,255,255),
800,2000);

ps->setEmitter(em);
em->drop();

scene::IParticleAffector* paf =
ps->createFadeOutParticleAffector();

ps->addAffector(paf);
paf->drop();

ps->setMaterialFlag(video::EMF_LIGHTING, false);
ps->setMaterialTexture(0, driver->getTexture("../../media/fire.bmp"));
ps->setMaterialType(video::EMT_TRANSPARENT_VERTEX_ALPHA);


마지막으로 살펴볼 특수 효과는 애니메이션 캐릭터에 적용되는 동적 그림자(dynamic shadow)이다. 이를 위해 퀘이크 2의 *.md2 모델을 읽고 월드 안에 넣겠다. 그림자를 만드는 것은 addShadowVolumeSceneNode()를 호출하여 쉽게 할 수 있다. (역자 주 : 그림자를 구현하는 방법은 여러가지가 있는데, 그중 예제에서는 Shadow Volume 기법을 사용한 것이다.) 그림자의 색깔은 ISceneManager::setShadowColor()에 의해 전역적으로 결정되어 있다. (역자 주 : 그림자 색깔은 하나로 정해져 모두 같다.) 자, 보라. 동적 그림자가 (이렇게 쉽게) 만들어 졌다.

사실 예제의 캐릭터는 전체 씬에 대비해 너무 작다. setScale()을 이용해 좀 더 크게 만들자. 그리고 캐릭터는 동적 광원에 의해 빛을 받으므로, 광원의 방향으로 법선들을 표준화(normalize)해야 한다. 이는 동적 광원을 받는 모델의 크기가 (1, 1, 1)이 아닌 경우 반듯시 필요하다. 그렇지 않으면 법선들이 지나치게 커진 바람에, 너무 밝거나 너무 어두울 것이다. (역자 주 : 법선은 각 폴리곤 면에서 앞면쪽의 벡터 방향을 의미한다. 법선은 빛과 재질을 계산하는데 들어가기에 모델 크기의 변환만큼 법선에 영향을 주는 광원과의 관계도 변화를 줘야 한다.)


// add animated character

mesh = smgr->getMesh("../../media/faerie.md2");
scene::IAnimatedMeshSceneNode* anode = 0;

anode = smgr->addAnimatedMeshSceneNode(mesh);
anode->setPosition(core::vector3df(-50,65,-60));
anode->setMD2Animation(scene::EMAT_STAND);
anode->setMaterialTexture(0,

driver->getTexture("../../media/Faerie5.BMP"));

// add shadow
anode->addShadowVolumeSceneNode();
smgr->setShadowColor(video::SColor(150,0,0,0));

// make the model a little bit bigger and normalize its normals
// because of this for correct lighting
anode->setScale(core::vector3df(2,2,2));
anode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);


이제 준비한 것을 그려보자.


scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
camera->setPosition(core::vector3df(-50,50,-150));

// disable mouse cursor
device->getCursorControl()->setVisible(false);


int lastFPS = -1;

while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, 0);

smgr->drawAll();

driver->endScene();

int fps = driver->getFPS();

if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - SpecialFX example [";
str += driver->getName();
str += "] FPS:";
str += fps;

device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}

device->drop();

return 0;
}


----------------------------------------------------------------------------

실제 게임에서는 여기서 언급된 파티클 씨앗을 이용한 특수 효과 툴을 만들어 사용하게 될 것이다.


이번 예제를 해석하면서 여러 용어를 한글화 하는데 이질감을 느꼈다. 실제 normalize는 표준화, 계량화라는 말보다 그냥 '노말라이즈'라고 한다. 또한 emitter등도 아마 그냥 '파티클 이미터'라는 식으로 말할 것 같다. 하지만 억지로 한글화 해 보았다.


그냥 이렇게 해석만 해서는 안 될 것 같다. 사실 아무 의미 없다. 개발자라면 당연히 이 코드들을 이용해 여러 조작, 변경, 프로젝트에 직접 구현 등을 해야할 것이다. 그러나 일단 어떤 기능이 있는지 알아두면 나중에 그것을 찾아보면 될 것이니 일단 계속 하기로 한다. 게다가 예제 작성자의 친절함, 예를 들어 한가지를 설명하더라도 실 개발에 쓰일 여러 방법을 보여준다는데서 감동하며, 그의 재치(?)가 얼마나 되는지 살펴보는 것도 도움이 될 것이다.


+ Recent posts