n_Sys 2008. 12. 30. 06:43

장면 생성하기

메쉬 생성

가장 먼저 해야 일은 그려질 잔디용 메쉬를 생성하는 입니다. 기본적인 아이디어는 서로 겹치는 3개의 사각형을 생성하는 입니다. 각각의 사각형은 잔디용 텍스쳐를 포함하게 것입니다. 그리하여 수직으로 내려다보지 않는이상 3D 잔디처럼 보일겁니다. 가장 쉽게 3개의 사각형을 겹치게 하는 방법은 일단 한개를 생성한다음 다른 하나를 60 돌려서 생성하고 세번째 사각형을 다시 60 돌리는 방법입니다.

이전 튜토리얼에 했던 처럼 객체를 생성하는데 ManualObject 사용하게 것입니다. 하지만 이전 튜토리얼과는 다르게 간단한 라인리스트 대신에 메쉬를 생성하게 것입니다(이것 역시 삼각형들을 구성하는데 있어서 인덱스버퍼를 필요로 합니다).

우선 가장 기본적인 변수들을 설정하는 것으로 부터 시작합니다. 앞으로 정의할 사각형을 회전시킴에 있어서 오우거에서 제공하는 Vector3 사원수를 사용하면 그에 필요한 수학을 대신 해결해 주므로 가장 쉽게 구성하는 방법이 될것 입니다. 여기에서의 평면 1개는 Vector3 X Z축으로 이루어 집니다. 그리고 사원수를 이용한 회전을 되풀이 합니다. createGrassMesh멤버함수에 다음코드를 추가하세요 :

       const float width = 25;

       const float height = 30;

       ManualObject mo("GrassObject");

 

       Vector3 vec(width/2, 0, 0);

       Quaternion rot;

       rot.FromAngleAxis(Degree(60), Vector3::UNIT_Y);

ManualObject 정의하는데 필요한 변수들 설정이 완료되었습니다. 이전 튜토리얼과는 다르게도 이번에는 객체에 재질을 입힐 계획입니다. RenderOperation 삼각형 리스트를 설정하게 되는데 필요한 정점들을 정의한 다음에는 반드시 사각형의 면을 구성하게 삼각형리스트를 정의해야 합니다 :

       mo.begin("Examples/GrassBlades", RenderOperation::OT_TRIANGLE_LIST);

       for (int i = 0; i < 3; ++i)

       {

각각의 사각형들은 각각의 코너를 담당하는 4개의 정점을 가집니다. 각각의 정점은 텍스쳐의 좌표정보도 가집니다. 튜토리얼에서 쓰이는 텍스쳐 좌표정보는 오우거샘플인 Examples/GrassBlades재질에 적용됩니다. 텍스쳐의 좌측상단은 (0,0)으로 매치되고 오른쪽 하단은 (1,1)입니다 :

           mo.position(-vec.x, height, -vec.z);

           mo.textureCoord(0, 0);

 

           mo.position(vec.x, height, vec.z);

           mo.textureCoord(1, 0);

 

           mo.position(-vec.x, 0, -vec.z);

           mo.textureCoord(0, 1);

 

           mo.position(vec.x, 0, vec.z);

           mo.textureCoord(1, 1);

사각형의 모든 코너들을 설정했으니 이제 면을 생성해 봅시다. 이전 튜토리얼에서 간단하게 소개드린것과 같이 면은 삼각형으로 구성됩니다. 그리고 앞을 가르키는 방향은 시계 반대방향으로 감겨져야 합니다. 하나의 사각형을 위해서는 2개의 삼각형이 필요합니다. 첫번째는 (0, 3, 1) 정점으로 구성되며 두번째는 (0, 2, 3)정점으로 구성됩니다. 항목들은 하나의 사각형을 구성합니다. 프로그램에서는 몇차례 루프를 돌면서 매번 4개의 정점을 생성시킵니다. 그러므로 적절한 정점을 선택하기 위해서 offset 변수를 사용해야 합니다 :

           int offset = i * 4;

           mo.triangle(offset, offset+3, offset+1);

           mo.triangle(offset, offset+2, offset+3);

지금 만들어질 사각형을 회전시킨다음에 계속해서 루프를 진행합니다. 루프가 끝난다음에는 반드시 ManualObject::end함수를 호출하여 객체생성을 완료합니다.

           vec = rot * vec;

       }

       mo.end();

Manual object 정의되었습니다. 이제 StaticGeometry 생성하는데 필요한 작업이 끝나갑니다. 마지막 남은작업은 ManualObject 메쉬로 변환시키는 작업입니다. ManualObject 바로 사용하는것 보단 메쉬가 출력하는데 있어서 효율적입니다. 이러한 작업을 위해서는 ManualObject::convertToMesh 만들어질 메쉬의 이름을 넣고 호출하기만 하면 됩니다 :

       mo.convertToMesh("GrassBladesMesh");

이것으로 잔디용 메쉬가 생성되었습니다. 만약 복잡한 구성의 메쉬를 이러한 방법으로 생성시켰다면 매번 프로그램에서 생성시킬 필요없이 파일로 저장하여 다시 로드하여 있도록 만들 수도 있습니다. 그러기 위해서 convertToMesh(실제코드에서는 쓰이지 않을것 입니다) 리턴값인 변환된 메쉬포인터를 MeshSerializer::exportMesh함수로 넣어주면 됩니다. 어떻게 하는지에 대한 예제입니다 :

       // Do not add to the code!

       MeshPtr ptr = mo.convertToMesh("GrassBladesMesh");

       MeshSerializer ser;

       ser.exportMesh(ptr.getPointer(), "grass.mesh");

이제 StaticGeometry 생성할 준비가 완료되었습니다.

 

정적지형 추가

createScene함수는 이미 여러번 다루어졌던 부분입니다. 평면, 로봇, 그리고 카메라를 설정하는것 등등의 코드에 대해서 다시 설명할 필요가 없으므로 튜토리얼이 시작되는 시점에서 미리 작성해 두었습니다. 계속 진행하기전에 추가된 코드들에 대해서 모르는 사항이 있다면 이해하고 넘어가도록 하세요.

지금 해야할 첫번째 과제는 앞서 생성시켰던 잔디용 메쉬로 엔티티와 StaticGeometry객체를 생성하는 입니다. StaticGeometry에는 한개의 엔티티만 사용될 입니다. createScene함수를 찾아서 가장 마지막 부분에 다음 코드를 추가하세요 :

       Entity *grass = mSceneMgr->createEntity("grass", "GrassBladesMesh");

       StaticGeometry *sg = mSceneMgr->createStaticGeometry("GrassArea");

 

       const int size = 375;

       const int amount = 20;

size 변수값은 얼마나 넓은 지역을 잔디로 덮을것인지, amount변수값은 한줄에 얼마나 많은 수의 객체를 StaticGeometry 넣을것인지에 따라서 달라집니다.

이제 크기와 StaticGeometry 원점을 정해야 합니다. 객체를 생성(StaticGeometry::build 이용하여)시킨 이후에는 더이상 원점이나 StaticGeometry 범위를 수정할 없게 됩니다. 원점은 StaticGeometry에서 설정된 지형범위의 좌측상단구석입니다. 만약 StaticGeometry 중앙에 위치시키고 싶다면 원점의 x, z좌표를 지역의 x, z크기의 절반으로 설정하시면 됩니다 :

       sg->setRegionDimensions(Vector3(size, size, size));

       sg->setOrigin(Vector3(-size/2, 0, -size/2));

객체의 중심이 (0, 0, 0)으로 설정됩니다. 3D공간상의 중심점으로 하고 싶다면 다음과 비슷한 방식으로 설정하면 됩니다 :

       // Do not add to the project!

       sg->setOrigin(Vector3(-size/2, -size/2, -size/2) + Vector3(x, y, z));

x, y, z점은 3D 공간상의 중심점을 가르킵니다. 그리고 구역설정시 수직높이도 정의되어야 합니다. SetRegionDimensions y값은 StaticGeometry 들어가는 가장 키가 객체의 높이값 이상을 가져야 합니다.

이제 다음으로 해야 일은 StaticGeometry 객체를 넣는일 입니다. 다음코드는 복잡해 보이는데 이유는 실제와 비슷하게 보이기 위해서 전체를 기준으로 격자간격으로 잔디를 지형에 삽입하는데 랜덤하게 x, z위치가 약간씩 차이나며 크기도 달라집니다. 코드에서 가장 중요한 부분은 StaticGeometry::addEntity부분을 이해하는 입니다 :

       for (int x = -size/2; x < size/2; x += (size/amount))

           for (int z = -size/2; z < size/2; z += (size/amount))

           {

               Real r = size / (float)amount / 2;

               Vector3 pos(x + Math::RangeRandom(-r, r), 0, z + Math::RangeRandom(-r, r));

               Vector3 scale(1, Math::RangeRandom(0.9, 1.1), 1);

               Quaternion orientation;

               orientation.FromAngleAxis(Degree(Math::RangeRandom(0, 359)), Vector3::UNIT_Y);

 

               sg->addEntity(grass, pos, orientation, scale);

           }

addEntity함수는 사용될 엔티티, 객체의 위치, 방향, 크기를 입력받습니다. StaticGeometry 정의할때 addEntity함수나 addSceneNode함수를 사용하게 입니다. addSceneNode함수는 엔티티들을 정적 지형에추가함에 있어서 위치, 방향, 크기를 수동으로 설정하지 않고 SceneNode 설정된 값으로 취급합니다. 만약 addSceneNode함수를 사용할 계획이라면 그것의 부모 SceneNode로부터 노드를 직접 제거해 줘야 한다는 점을 유의하세요. 만약 제거해 주지 않는다면 오우거는 StaticGeometry외에도 그려져서는 안될 원래의 SceneNode까지 출력시킵니다.

이제 마지막으로 출력되기 전에 StaticGeometry 빌드해줘야 합니다:

       sg->build();

컴파일 실행시켜 보세요. 작은 잔디밭에 서있는 로봇하나를 발견할 있을겁니다.