기초 튜토리얼 2-2

Ogre3D 삽질란/Basic Tutorial 2 2008. 11. 8. 17:12

 

Viewports

 

Ogre Viewports

여러개의 카메라를 다룰경우 뷰포트 클래스 개념은 무척이나 유용하게 쓰일 입니다. 이야기를 지금 꺼내는 이유는 오우거가 장면을 그릴때 사용할 카메라를 어떻게 선택하는지를 이해하는것이 중요하기 때문이죠. 한번에 여러개의 SceneManager 실행하는것이 가능합니다. 하나의 스크린을 여러개의 영역으로 분할해서 각각의 카메라들이 각각의 영역을 그리는것 (예를 들면 콘솔게임에서 하나의 화면에서 2인플레이를 하는것) 역시 가능합니다. 이러한 작업들이 가능하지만 세부적인 방법은 Advanced 튜토리얼에서 다루게 입니다.

오우거가 어떻게 장면을 그리는지를 이해하려면 오우거의 3가지 구성요소를 알아야 합니다 : Camera, SceneManager 그리고 RenderWindow 입니다. RenderWindow   다루지 않았습니다만 간단하게 말하면 모든것이 출력되는 윈도우라 생각하시면 됩니다. RenderWindow 에게 어떤 카메라가 화면에 출력할지를 알려줘야 하며 출력될 화면의 영역도 알려줘야 합니다. RenderWindow 에게 알려주는 카메라 출력 영역은 유저의 뷰포트 입니다. 일반적인 오우거 사용에 있어서는 보통 하나의 카메라를 생성하고 화면 전체를 사용하는 RenderWindow 등록하므로 하나의 뷰포트 객체를 가지게 됩니다.

이제 뷰포트를 생성하기위해 카메라를 어떻게 등록하는지를 다뤄 봅시다. 다음 그려질 장면의 배경색을 설정하기위한 뷰포트 오브젝트를 사용할 있습니다.

 

Viewport 생성

ExampleApplication 뷰포트 생성부분을 재정의 하기위해 TutorialApplication::createViewports 함수로 갑시다. 뷰포트를 만들기 위해서는 단순히 RenderWindow addViewport 함수를 호출하고 어떤 카메라가 사용되는지만 알려주면 됩니다. ExampleApplication 클래스에는 이미 mWindow 클래스가 RenderWindow 함께 자리잡고 있으므로 그냥 다음 라인을 추가하세요 :

        // Create one viewport, entire window

        Viewport* vp = mWindow->addViewport(mCamera);

이제 뷰포트를 만들었습니다만 이걸로 할수 있을까요? 대답 : 별로 할게 없습니다. 가장 중요한건 setBackgroundColour 함수로 배경색을 자유롭게 정할 있다는 입니다. 조명 튜토리얼에서 했던 처럼 배경색을 검정으로 지정해 봅시다 :

        vp->setBackgroundColour(ColourValue(0,0,0));

ColourValue red, green 그리고 blue 색상값 가지는데 0 1 사이의 값을 취합니다. 마지막으로 해야할게 있는데 바로 카메라의 화면 종횡비율 설정입니다. 만약 정상적인 전체화면 뷰포트의 비율과 다르게 설정하면 장면이 이상하게 보일 입니다. 초기값 종횡비율을 사용하게 것이지만 일단 한번 설정 봅시다 :  

        // Alter the camera aspect ratio to match the viewport

        mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));   

긴단한 뷰포트 클래스의 사용법을 끝냈습니다. 비록 화면에 나오는게 아무것도 없지만 적어도 어플리케이션을 컴파일 하고 실행할 있게 되었습니다(ESC 누르면 종료). 다음 과정으로 넘어가기전에 실행이 되는지를 확인하세요.

 

조명과 그림자

 

오우거에서 지원하는 그림자 타입들

오우거는 현재 3가지 종류의 그림자를 지원합니다 :

사용자 삽입 이미지


1.    Modulative Texture Shadows (SHADOWTYPE_TEXTURE_MODULATIVE) - 3가지 방식중에서 가장 적은 계산량을 요구합니다. 흑백 방식의 그림자를 생성한 다음에 장면에 적용합니다.

2.    Modulative Stencil Shadows (SHADOWTYPE_STENCIL_MODULATIVE) - 기법은 불투명 객체들이 화면에 그려지고 다음에 그림자가 있어야 자리는 따로 다시 계산하여 최종적으로 출력합니다. Additive Stencil Shadows 만큼 부하가 걸리지는 않지만 만큼 세밀한 그림자 출력을 해주지는 않습니다.

3.    Additive Stencil Shadows (SHADOWTYPE_STENCIL_ADDITIVE) - 기법은 각각의 조명이 따로 화면을 그리는 과정으로 처리 됩니다. 하나의 조명은 한번의 전체렌더링을 그리는 만큼의 처리량을 필요로 하기때문에 그래픽카드에 많은 부하가 걸리게 됩니다.

Soft shadow 그림자는 엔진의 기본기능으로는 제공하지 않습니다. 만약 Soft shadow 사용하시고 싶으시면 사용자가 따로 vertex fragment 코드를 작성해야 합니다. 여기서는 이것에 대한 간단한 소개만 것입니다. 오우거 메뉴얼을 참고하시면 오우거에서의 그림자들에 대한 간략한 사용법이 모두 설명되어 있습니다.

 

오우거에서 그림자 사용하기

오우거에서 그림자를 쓰는것은 비교적 쉽습니다. SceneManager 클래스는 어떠한 그림자를 사용할지를 설정해 주는 setShadowTechnique 멤버 함수를 가지고 있습니다. 다음에 언제든지 Entity 만들면 setCastShadows 함수를 호출하는것으로 그림자를 생성하거나 숨길 있습니다.

이제 주변광을 완전한 어둠으로 설정하고 사용하게 그림자를 선택합니다. TutorialApplication::createScene 함수로 가서 다음 코드를 입력해 주세요 :

        mSceneMgr->setAmbientLight(ColourValue(0, 0, 0));

        mSceneMgr->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE);

이제 SceneManager additive stencil shadows 사용하게 됩니다. 어서 객체를 화면에 띄워놓고 그림자를 발생시켜 봅시다 :

        Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");

        ent->setCastShadows(true);

        mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(ent);

설명하자면 ExampleApplication 클래스는 미리 ninja.mesh 메모리에 로드시켜놓았습니다. 그것도 좋지만 우리는 닌자가 있을만한 뭔가가 필요합니다(그림자가 뿌려질 뭔가를 말이죠). 그렇기에 우리는 닌자가 있을만한 간단한 평면을 생성할 입니다. MeshManager 기반으로 진행되는 튜토리얼이 된다는 의미는 아니지만 평면을 생성하기위해서 최소한의 기본은 사용할 입니다. 가장먼저 평면 객체 자체를 정의하고 원점으로부터 떨어진 거리, (Normal)법선벡터를 설정해야 합니다. 평면을 지형의 일부분으로 사용할 있으며, 경우 원점으로부터 어느정도 떨어져서 위치하도록 해야 필요도 있습니다. 우리는 +y 방향을 향한(앞면이 위로 향한) 평면이 필요하고 원점에 위치하게 입니다 :

       Plane plane(Vector3::UNIT_Y, 0);

어플리케이션에서 있도록 평면을 등록합시다. MeshManager 클래스는 어플리케이션에서 읽어드린 모든 메쉬들을 관리합니다(robot.mesh ninja.mesh 같이 읽어들였던 ). createPlane 멤버함수는 평면의 정의와 매개변수로 부터 메쉬를 생성합니다. 평면을 쓰기위해 등록합니다 :

       MeshManager::getSingleton().createPlane("ground",

           ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,

           1500,1500,20,20,true,1,5,5,Vector3::UNIT_Z);

          

다시 언급하지만, MeshManager 어떻게 사용되는지를 아직은 알려고 하지 마세요 ( 매개변수들이 정확히 하는지 알고 싶다면 API 레퍼런스를 참고하세요). 간단하게 보자면 1500 x 1500 크기의 "ground" 라고 불리는 평면메쉬를 등록했습니다. 이제 메쉬로 Entity 만들고 장면에 올려봅시다 :

       ent = mSceneMgr->createEntity("GroundEntity", "ground");

       mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(ent);

깔끔하죠? "ground" 위해서 해야할게 2 남았습니다. 첫째는 SceneManager에게 그림자가 표시될 영역이기 때문에 지면자체가 그림자를 생성할 필요가 없다고 알리는 입니다. 두번째는 텍스쳐를 입히는 입니다. 우리가 쓰는 로봇과 닌자의 메쉬는 재질 스크립트가 자체적으로 이미 설정되어 있습니다. 우리가 수동으로 "ground" 메쉬를 생성할 어떤 텍스쳐가 사용될지는 설정하지 않았습니다. 이것에는 오우거 샘플에 포함된 "Examples/Rockwall" 이름의 재질 스크립트를 사용 것입니다 :

       ent->setMaterialName("Examples/Rockwall");

       ent->setCastShadows(false);

이제 지면과 닌자를 장면에 올려놨으니 컴파일하고 실행시켜 봅시다. , 거기 멋진 닌자가.. 안보입니다! 어떻게 된거죠? 이전 튜토리얼에서는 로봇을 넣고 보였습니다. 닌자가 보이지 않는 이유는 주변광을 완전깜깜한 어둠으로 설정했기 때문입니다. .. 조명을 추가하고 뭐가 어찌 돌아가는지 봅시다.

 

문제점 해결

몇몇 분들께서 부분을 진행함에 있어서 code::blocks 에서 GCC 사용할 경우 문제점이 있다고 알려줬습니다. 이런 비슷한 에러 메세지를 발견 하신다면 :  

variable 'vtable for Ogre::MeshPtr' can't be auto-imported. Please read the documentation for ld's --enable-auto-import for details.

Linker 옵션에 다음줄을 추가해 주세요 :

-Wl,--enable-runtime-pseudo-reloc

codeblocks::ide 사용한다면 project name -> build options -> linker 옵션으로 가서 설정하세요.

 

조명 종류

오우거는 3가지 종류의 조명을 제공합니다.

1.    Point (LT_POINT) - 한개 지점에서 사방으로 빛이 퍼져 나옵니다.

2.    Spotlight (LT_SPOTLIGHT) - spotlight 정확하게 손전등처럼 동작합니다. 빛이 나가는 점이 있고 가리키는 방향쪽으로 빛이 진행됩니다. 안쪽과 바깥쪽의 빛의 퍼짐 각도도 절정할 있습니다(손전등은 주변부 빛보다 중심쪽이 밝다는 사실을 아십니까?)

3.    Directional (LT_DIRECTIONAL) - 방향성을 가진 조명은 한개방향으로 빛이 진행되면서 부딛히는 모든것을 시뮬레이트 줍니다. 예를 들면 밤의 배경에서 달빛을 구현하고 싶습니다. 옅은 주변광으로 비슷하게 구현할 있을지 모르겠지만 달빛이 모든 사물을 똑같이 비춘다는 (해도 똑같습니다) 에서는 비현실 적입니다. 한가지 방법은 달이 빛나는 지점과 방향성을 가진 조명을 설정하는 입니다.

조명이 어떻게 보일지 설정하는데에는 무척이나 넓은 변수가 작용합니다. 가장 중요한 2가지 조명속성으로는 산란(diffuse) 정반사(specular) 색상입니다. 각각의 재질은 반사될때 어느정도의 산란광 정반사광 수치를 가지는지 정의되는데 추후 튜토리얼에서 어떻게 제어하는지를 다룰 예정입니다.

 

조명 생성

조명생성을 위해서는 SceneManager createLight 멤버함수에 조명이름을 제공하여 만드는데 Entity Camera 생성하는것과 배우 유사합니다. 조명생성후 위치를 설정하는데 수동으로 할것인지 SceneNode 붙여서 이동하는데 유리하게 할지를 선택할 있습니다. 카메라 오브젝트와는 다르게 조명은 setPosition setDirection 가능합니다(게다가 translate, pitch, yaw, roll 등과 같은 움직임과 관련된 함수들도 없습니다). 그래서 만약 정적인 조명을 만들어야 한다면 setPosition 멤버함수를 호출하면 됩니다. 만약 움직이는 조명이 필요하다면(캐릭터를 따라다니는 조명같은) setPosition 호출 대신에 SceneNode 붙여야 합니다.

기본적인 point 조명을 만들어 봅시다. 첫번째로 일은 조명을 만들고, 타입을 설정하고, 위치를 설정하는 입니다 :

       Light *light = mSceneMgr->createLight("Light1");

       light->setType(Light::LT_POINT);

       light->setPosition(Vector3(0, 150, 250));

산란광과 반사광을 설정할 있는 조명이 만들어 졌습니다. 빨간 조명으로 만듭니다 :

       light->setDiffuseColour(1.0, 0.0, 0.0);

       light->setSpecularColour(1.0, 0.0, 0.0);

이제 컴파일후 실행시켜 봅시다. 해냈습니다! 이제 닌자와 그림자를 있습니다. 닌자의 정면으로 카메라를 옮겨서 전체적인 모습을 보세요. 관심있게 보셔야 점은 광원을 없다는 입니다. 빛이 생성하는 것은 보이지만 자체는 보이지 않습니다. 많은 오우거 튜토리얼에서는 빛이 어디에서 비추고 있는지를 보여주기 위해 간단한 Entity 추가합니다. 만약 조명과 관련해서 어려움이 발생한다면 조명이 정확하게 어디에 있는지를 확인할 있도록 뭔가를 생성하는것을 고려할 필요성이 있습니다.

다음으로 방향성을 가진 조명을 시도해 봅시다. 닌자의 정면이 정말 새까맣죠? 희미한 노란색 조명을 정면방향에서 비춰 봅시다. 조명을 만들고 색상은 point 조명에서 했던방법 처럼 설정합니다 :

       light = mSceneMgr->createLight("Light3");

       light->setType(Light::LT_DIRECTIONAL);

       light->setDiffuseColour(ColourValue(.25, .25, 0));

       light->setSpecularColour(ColourValue(.25, .25, 0));

방향성을 가진 조명은 거리와는 관계없이 동일하게 적용되므로 위치를 설정할 필요없이 방향만 설정해 주면 됩니다. 지금 생성하는 조명은 +z 에서 -y 방향으로 비추게 것입니다 (닌자의 45 -정면 방향에서 빛이 내려온다고 생각하시면 됩니다) :

       light->setDirection(Vector3( 0, -1, 1 ));

컴파일하고 실행시켜 봅시다. 화면상 2개의 그림자를 있습니다. 방향성 조명이 희마한 만큼 그림자도 희미합니다. 마지막으로 살펴볼 조명종류는 spotlight 입니다. 이제 spotlight 생성해 봅시다 :

       light = mSceneMgr->createLight("Light2");

       light->setType(Light::LT_SPOTLIGHT);

       light->setDiffuseColour(0, 0, 1.0);

       light->setSpecularColour(0, 0, 1.0);

spotlight 빛을 방향과 위치 둘다 설정해야 합니다. 닌자의 오른쪽 어께위로 있는 spotlight 생성하고 닌자를 향해서 빛을 발산할 입니다 :

       light->setDirection(-1, -1, 0);

       light->setPosition(Vector3(300, 300, 0));

Spotlight 광선이 얼마나 넓게 퍼질지도 설정할 있습니다. 손전등을 잠시 떠올려 보세요. 광선의 중심은 주변을 감싸는 빛보다 밝습니다. 광선의 넓이요소들을 setSpotlightRange 멤버함수를 호출하여 설정할 있습니다 :

       light->setSpotlightRange(Degree(35), Degree(50));

컴파일하고 실행시켜 보세요. 보라색 닌자... 살벌하군요!

: