태그 : 3D

[Irrlicht] 3D Cube 구현



이전글에 이어서 3D Cube의 구현에 대해 써보자 합니다.

이전글을 보시면 아시겠지만, 큐브의 조작은 마우스로만 이루어집니다.
즉, 마우스 입력을 큐브회전으로 변환하는 것이 핵심이죠.
그러므로 잡다한 자료구조나, 구조는 제외하고 이것에 대해서만 포스팅하겠습니다.



우선 하나의 면을 클릭해보죠.
스샷에서 하얀색으로 표시된 부분이 클릭한 면입니다.
그리고 이때  마우스로 조작할 수 있는 회전은 3가지입니다. (반대방향은 제외)
하늘색면을 회전, 녹색면을 회전, 분홍색면을 회전이죠.


하늘색면의 회전에 대해서 생각해 보도록 합시다.
아까의 위치를 클릭하였을 때, 4개의 벡터를 얻을 수 있습니다.
이 4개의 벡터중 하나를 이용해서 회전하고자 하는 면을 선별하는 것입니다.
이 벡터들은 클릭한 조각의 각각 면의 법선을 의미합니다.


그리고 조각을 선택한 상태에서 왼쪽으로 마우스를 움직여보죠. 
그러면 처음에 마우스를 클릭한 지점부터, 마우스를 땐 지점까지
붉은색 화살표와 같은 벡터를 얻을 수 있습니다.
그리고 이 벡터를 기초로, 아까구한 4개의 벡터 중, 가장 유사한 하나의 벡터를 선별합니다.
[선별하는건 일반적인 피킹과정과 유사합니다. 붉은 화살표는 카메라좌표계의 벡터이므로
 이것을 선택한 조각의 로컬좌표계로 이동시킨후, 가장 가까운 벡터를 찾는 것이죠. ]
[가장 가까운 벡터를 찾는 것도 간단합니다. 각각의 노멀벡터를 하나의 위치로서 사용하여
 변환한 붉은 벡터의 노멀(이것또한 위치로 사용)과 가장 가까운 벡터를 찾으면 됩니다.)


선별된 벡터와 클릭지점의 노멀벡터를 사용하면 회전하고자 하는 면들을 구합니다.
상단에 검은 사각형들이 바로 이번 입력에 의해 회전될 사각형들입니다.


그렇다면 회전축을 찾아야겠죠?
회전축 찾은 것또한 간단하죠.
단순히 위에서 구한 벡터 2개의 외적으로 구해지는 범선벡터를 사용하는 것입니다.
외적으로 회전축을 구하는 것은 또 하나의 이득이 있습니다.
반대로 드래그했다면 파랑색벡터는 반대방향이 되어있을 것입니다.
이들의 외적은 녹색벡터와 반대방향이 되어있겠죠.
당연히 녹색벡터의 반대방향 벡터로 회전을 하면 반대로 회전하게 됩니다.


이러식으로 구해진 큐브조각을 정해진 회전축으로 회전시키면 큐브가 회전하게 됩니다.
이렇게 만든 루틴은 녹색을 회전할때도 같은 방법으로 회전이 가능합니다.


이러한 방식이 통하지 않는 건 분홍색면을 회전할 때가 됩니다.
일반적으로 분홍색면을 클릭하고 분홍색을 회전한다면, 마우스로 원을 그리는게 타당하겠죠.
만약 이 기능을 넣는다면 마우스의 회전을 감지하고, 클릭한 면의 노멀로 회전.
그리고 다른 면의 노멀로, 회전할 조각을 검색하는것으로 구현이 가능하겠죠.

그러나 분홍색과 녹색을 포함한 조각을 밑으로 내리는게 마우스 동선이 더 짧기도 합니다.
또 별개의 마우스 움직임을 추가하면, 움직임 판정이 미묘해 질 가능성도 있겠죠.
그래서 이러한 회전은 구현에서 제외하였습니다. (슬슬 다음계획으로 넘어가고 싶었고;;)


이렇게 회전처리를 하면 카메라 위치와 관계없이, 동일한 룰로 회전을 처리할 수 있습니다.
카메라의 위치와 관계없이 직관적으로 큐브를 조각할 수 있는 것이죠.



3DCubeSource.zip 소스입니다.
일리히트 엔진을 이용하였습니다. 일리히트엔진을 알아보는 김에 한번 작업해봤습니다.
처음 써본거라 조작에 미숙한 점이 있더라도 향해바랍니다 ^^;

참고로 만약에 DirectX나 OPEN GL등의 과제로 쓴다면 거의 다 뜯어고쳐야 할겁니다.
(보통 3D수업은 API레벨일테니, 과제용으로 써먹기 힘들거 같아서 공개하는거죠 ㅎㅎ)



학기말 텀프로젝트때, 큐브만든다는 친구가 있었는데, 재미있어보여서 한번 짜봤습니다.
혹시 다음학기때, 인공지능땜에 써먹을 수 있을지 모르겠네요;;

by hellz | 2009/07/09 01:04 | Workshop | 트랙백 | 덧글(1)

[Irrlicht] 3D Cube

3DCube.zip
3D 큐브입니다.
마우스로 원하는 면을 드레그하면 회전하도록 되어있습니다.
오른쪽클릭으로 큐브를 회전할 수 있습니다.

제목 그대로 큐브의 움직임만 재현한 프로그램입니다.
목표가 움직임 재현이어서, 큐브를 섞거나, 푸는 등의 부가기능은 넣지 않았습니다.
사실 남은 부가기능은 잔 작업이니 말이죠 --; ( 푸는 법은 좀 연구해야겠지만;;)

구현에 관한 사항은 별도로 올릴 생각입니다.


by hellz | 2009/07/08 23:59 | Workshop | 트랙백 | 덧글(0)

[3D][DirectX] 물에 잠기는 알파오브젝트

드디어 한학기가 끝났습니다.
시험이다 과제때문에 한동안 포스팅이 없었네요. (정기방문보다 검색방문이 많겠지만 ;;;)
기말고사는 저번주에 끝났지만, 늦게 내는 텀이 남아서 방학(!)이  한주 더 걸렸네요.
이제 남은건 성적공개뿐이군요....



알파오브젝트는 일반적인 오브젝트를 그린 후,
알파 오브젝트끼리 카메라에서의 거리로 먼곳부터 그려줘야 합니다.

문제는 지형에 커다란 사각형으로 물을 씌울때 발생합니다.
물도 알파오브젝트인데, 다른 작은 알파오브젝트와 비교해 언제 그려줘야 할까요?




빌보드로 알파값을 가진 나무입니다.
나무를 먼저 그려주고, 물을 그려준 상태입니다.
보시다시피 빌보드의 투명부분에 물이 그려지지 않았습니다.
나무의 Z버퍼가 물보다 앞에 있기때문에 그려지지 않는것이죠.



이번엔 반대로 물을 그려주고, 나무를 그려줬습니다.
얼핏보면 제대로 그져진듯 하지만, 물에 잠긴 나무밑둥이 그려지지 않습니다.
아까와 반대로 물의 Z버퍼가 잠긴 나무앞에 있기 때문에 그려질 수 없는것이죠.
만약 물이 투명하지 않는 거라면 이정도로도 충분할 겁니다.
하지만 투명히 비추는 물에 잠긴 오브젝트를 원한다면 불충분하죠.



해결법은 나무를 2번 그리고, Z버퍼쓰기 상태를 이용하는 겁니다.
먼저 나무를 그려줍니다.
중요 포인트는 Z버퍼쓰기를 꺼주는 겁니다.
SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
이렇게하면 출력은 되지만, Z버퍼에는 반영이 되지 않죠.


다시 Z버퍼쓰기를 활성화한 후,  물을 그려줍니다.
물 위쪽에 그려진 나무가 물과 알파블렌딩되서 흐려진것을 알 수 있습니다.
당연히 물에 잠긴 밑둥은 물과 블렌딩된 상태입니다.


마지막으로 나무를 다시 그려줍니다.
물을 그릴때 Z버퍼가 변하여, 밑둥이 다시 그려지지 않고,
잠긴 효과 그대로 유지되는것을 알 수 있습니다.
물 위 가지부분은  물보다 카메라에 가까우므로, 덮어 써지는것을 알 수 있습니다.


정리하면 다음과 같습니다.
  1. Z버퍼쓰기를 끄고 알파 오브젝트를 그린다. (Z버퍼 사용[D3DRS_ZENABLE])이 아님.)
  2. Z버퍼쓰기를 다시 키고 물을 그린다.
  3. 알파 오브젝트를 다시 그린다.


이걸 생각해내기 전에는 요상하게 작성해봤었죠.
Z버퍼쓰기를 끄고, 물을 그리고, Z버퍼쓰기 키고 오브젝트를 그리고 알파블렌딩을
D3DBLENDOP_MIN로 지정한뒤 물을 다시 출력했었습니다.
이렇게하면 오브젝트의 알파부분과 물과 약간의 색차가 존재합니다.
그래도 멀리서 보면, 어느정도 그럴듯 했었죠;;;;

원래 과제에 덤으로 넣은 물효과인데, 그냥 초안대로 작성해서 빨리 내버렸습니다.
근데 제출한 다음날에 이방법이 떠올랐다죠 orz...
다행히 기말 실기시험에서 이 소스를 사용해서, 기말고사는 수정된 버전으로 제출~




by hellz | 2009/06/26 23:51 | Programming | 트랙백 | 덧글(0)

[3D][DirectX] Solar System

과제로 나온 태양계입니다. 과제한지는 꽤 됐는데, 그다지 시간여유가 없었습니다 --;


우선 태양계를 살펴보면 항성인 태양. 지구같은 행성. 달과 같은 위성이 있습니다.
지구는 태양주위를 공전하며, 달은 지구 주위를 공전하죠.
또한 자신조차도 스스로 공전을 합니다.

즉, 혹성은 부모주위를 공전하며, 자전을 하죠.
이것을 정리하여 항성과 행성과 위성을 나타내는 혹성클레스를 정의합니다.
혹성안에는 공전과 자전을 위한 자료. 그리고 자식들의 리스트를 가지고 있습니다.
그리고 혹성의 움직임, 그리기등의 작업은 부모를 통해 자식에게 전해지게 됩니다.

그리기 코드는 다음과 같습니다.

bool CPlanet::Draw(LPDIRECT3DDEVICE9 pD3DDevice, D3DXMATRIXMotherTranslation) {

             pD3DDevice->SetTransform(D3DTS_WORLD,

 &(SelfRotation* MotherRotation * MotherTranslation));

             this->m_pMesh->Render(pD3DDevice);

 

             for(int Count = 0;Count < Child.size(); Count++)

Child[Count]->Draw(pD3DDevice, this->MotherRotation * MotherTranslation);   

return true;

}

부모의 위치좌표를 넘겨 받습니다.

그리고 자전회전 * 공전회전 * 부모위치을 차례로 곱해서 혹성의 위치를 구합니다.

자신을 그리면 자전을 제외한 위치행열을 자식에게 전달합니다.


이렇게하면 모든 혹성을 루프를 통해 일괄적으로 넣을수도 있죠.




실행파일 (LabProject.exe)

숫자키 1~8을 누르면 각 행성으로 카메라가 이동되며
9번은 상단카메라. 0번은 위에서 비스듬하게 내려다 보는 카메라입니다.
구는 버텍스와 인덱스를 실린더 공식을 이용해서 직접 만들어 주었는데,
회전을 명확히 보기 위해 색상에 노이즈를 넣어봤습니다.


원래는 구 코드도 올릴까 했는데, 이건 나중에 시간되면 올리죠 --;
일단 다음과제 올리는건 건너뛰고(제공된 소스를 활용한 수준이라)
다음에 올릴건 피킹이 되겠군요.. 일단 구현은 완료된 상태...

by hellz | 2009/05/25 00:16 | Workshop | 트랙백 | 덧글(0)

[3D][DirectX] Cylinder World

과제로 나온 Cylinder를 사용한 프로그램을 작성했봤습니다.


Cyliner를 만드는건 간단합니다.
우선 원의 한 지점을 구하기 위해서 세타값을 구합니다.
이것은 간단히 원통의 단계를 PI로 나누어 단위세타를 구하고, 단계마다 곱해줍니다.
그리고 이것을 높이의 양 끝에 배치해서 순서대로 넣어주면 됩니다.
렌더링엔 TRIANGE_STRIP을 사용합니다.
    for(int CreateCount = 0;
        CreateCount < Division;
        CreateCount++) {
            float Theta = 2 * D3DX_PI * CreateCount / (Division - 1);
            this->m_pVertex[CreateCount * 2    ] =
                CVertex(sinf(Theta) * CircleRadius,  Height, cosf(Theta) * CircleRadius, UpColor);
            this->m_pVertex[CreateCount * 2 + 1] =
                CVertex(sinf(Theta) * CircleRadius, -Height, cosf(Theta) * CircleRadius, DownColor);
    }

그리고 XZ를 평면으로 가정하고 그 위에서 Cyliner가 굴러가도록 구현했습니다.
상하좌우키로 평면위를 움직이는데, 다른 방향의 입력이 들어오면 회전합니다.
회전엔 제한을 걸었습니다. 회전을 Cyliner의 중점을 기준으로 회전하는 것이죠.
원래 회전하고자 하는 방향의 끝을 기준으로 회전인데, 코드가 매우 늘어나더군요.
게다가 시험기간이라서 간단히 회전했습니다. 덧붙여 상하<->좌우회전시 회전방향도
고정해버렸습니다. 고정을 하지 않으면 원통이 뒤집힐 수 있기때문이죠.
  이동(방향에 따라 회전축이 바뀝니다. 아래소스는 한방향만 표시)
D3DXMatrixTranslation(&Trans, 0.0f, 0.0f, CylinderMovement);
D3DXMatrixRotationY(&Rot, CylinerRoll); 
this->mRollCylinder.m_mtxWorldTranslation *= Trans;
this->mRollCylinder.m_mtxWorldRotation = Rot * this->mRollCylinder.m_mtxWorldRotation;
this->mRollCylinder.CalculationWorld();
  회전

if(this->mBreadth== true)

this->mRollCylinder.m_mtxWorldRotation*= this->mAnimationBreadthRollMatrix;

else

this->mRollCylinder.m_mtxWorldRotation*= this->mAnimationNonBreadthRollMatrix;


굴러가는 범위제한은 두가지 방법으로 해봤습니다.
하나는 버텍스별로 월드에 배치시켜 해당범위를 체크하는 것이고,
또 다른 하나는 중점을 월드에 배치시켜 범위를 체크하는것입니다.
전자의 경우, 이동에 사용해봤고, 후자는 회전에 사용했습니다. 사실 후자가 간단하죠;;

D3DXMatrixTranslation(&Trans, MovementX, 0.0f,MovementZ);

Trans = this->mRollCylinder.m_mtxWorld* Trans;

// 필요한 버텍스마다 체크

D3DXVec3TransformCoord(&TestVec3, &TestVec3,&Trans);

// 이동 범위검사.

D3DXVECTOR3 TestVec3(0.0f, 0.0f, 0.0f);  

D3DXVec3TransformCoord(&TestVec3, &TestVec3, &this->mRollCylinder.m_mtxWorld);

// 회전 범위검사



추가로 움직이는 Cylinder를 향해 총알이 발사되도록 해봤습니다.
우선 총알을 배치해야 하는데, 이것은 XZ평면 위쪽에서 일정범위 안에서
무작위로 생성되도록 해봤습니다. (New-에서 랜덤좌표를 구해넣습니다.)
그리고 이것이 움직일 수 있는 Cylinder를 행해 이동할수 있는 이동행열을 구합니다.

D3DXVECTOR3 Target(0.0f, 0.0f, 0.0f);        

D3DXVec3TransformCoord(&Target, &Target, &this->mRollCylinder.m_mtxWorld);

D3DXVECTOR3 Bullet(NewX, NewY, NewZ);

D3DXVECTOR3 TargetDirection = Target - Bullet;

D3DXVec3Normalize(&TargetDirection, &TargetDirection);

D3DXMatrixTranslation(&mSpeedMatrix[Index], TargetDirection.x* Speed,

                           TargetDirection.y* Speed, TargetDirection.z * Speed);                         



그리고 이 총알을 해당방향으로 회전해야 합니다.
카메라의 역행열은 월드배치이므로
D3DXMatrixLookAtLH을 이용했습니다.
위에서 시작점과 목표점을 구했으니 이 함수에 직접 인자를 넣을수 있으니깐요.
물론 이전포스팅처럼 RIGHT, UP, LOOK벡터를 구해서 행열에 넣어도 가능합니다.
모델좌표의 Look는 Z축이고,  실제총알의 Look는 Y축이므로 X축 회전이 추가됩니다.

D3DXMATRIX NewTrans, NewRot1, NewRot2;           

D3DXMatrixTranslation(&NewTrans, NewX, NewY, NewZ);

D3DXVECTOR3 Pos(NewX, NewY, NewZ);

D3DXVECTOR3 LOOK = Target;

D3DXVECTOR3 UP;

D3DXVec3Cross(&UP, &Pos, &LOOK);

D3DXMatrixLookAtLH(&NewRot1, &Pos, &LOOK,&UP);

NewRot1._41 = 0.0f;          NewRot1._42= 0.0f;          NewRot1._43 = 0.0f;

D3DXMatrixInverse(&NewRot1, NULL, &NewRot1);

D3DXMatrixRotationX(&NewRot2, D3DX_PI / 2);

this->mBulletCylinder[Index].m_mtxWorld = NewRot2 *NewRot1  * NewTrans; 


이렇게 총알변환행열을 구했으면 카메라를 총알을 따라가게 하는 것은 쉽죠.
월드의 역변환이 카메라의 행열이 되니깐요.

D3DXMatrixInverse(&this->mView,NULL, &this->mBulletCylinder[0].m_mtxWorld);





결과
과제문서로 제출했던 그림을 다시 따오니 압축이 좀 심하게 들어가있네요.. ex2.exe


어쨌건 첫번째 과제는 이렇게 넘어가네요. 총알을 구현한 덕분에 텀프로젝트에선
탄환도 추가할수 있을듯 싶습니다. 물론 회전을 무식하게 구현한만큼 좀더 공부해서
다듬어야 겠지만요 --;

by hellz | 2009/04/23 12:36 | Workshop | 트랙백 | 덧글(0)

◀ 이전 페이지          다음 페이지 ▶