상세 컨텐츠

본문 제목

리듬게임을 만들어보자 6. 직선, 사각형 그리기

프로그래밍/스페샬

by ∫2tdt=t²+c 2009. 11. 29. 21:11

본문

그래픽 처리를 제대로 하자면, 미리 준비되어있는 그림파일을 화면을 그리는 것 말고도, 직선을 그리는 것이나, 사각형을 그리는 것 등등이 필요하다.
이번에는 직선과 사각형 그리는 것을 구현해보자.

D3D에서는 직선을 쉽게 그릴 수 있도록 ID3DXLine이라는 인터페이스를 제공한다.
ID3DXLine은 D3DXCreateLine함수를 이용하여 간단히 만들수 있다.

LPD3DXLINE pline;
D3DXCreateLine(pd3dd, &pline);

참, 쉽죠?

ID3DXLine으로 직선을 그리는 순서는 다음과 같다.

1. ID3DXLine::Begin함수로 그리기를 준비한다.
2. ID3DXLine::Draw함수로 직선을 그리고 싶은 만큼 그린다.
3. ID3DXLine::End함수로 그리기를 마친다.

ID3DXSprite 인터페이스와 사용법이 매우 비슷하므로, 쉽게 쉽게 쓸 수 있을 것이다.
(근데 사실 Begin/End함수를 호출안하고 Draw함수를 호출해도 된다. 그럴 경우에는 Draw함수 내부에서 알아서 Begin/End함수를 호출해준다.)
ID3DXSprite와 마찬가지로, ID3DXLine도 장치를 잃었을 경우에는 OnLostDevice함수를 호출해 해제하고, 장치를 복구했을 때는 OnResetDevice함수를 호출해 복구시켜줘야 한다.

자, ID3DXLine::Draw함수를 자세히 뜯어보자.

HRESULT Draw( CONST D3DXVECTOR2* pVertexList, DWORD dwVertexListCount, D3DCOLOR Color );

pVertexList는 점들의 배열이다. 그냥 직선을 그릴거라면 점이 2개면 족하겠지만, 직선이 계속계속 이어진다면 얘기는 달라진다.  pVertexList[0]~pVertexList[1]을 잇는 직선을 그리고, pVertexList[1]~pVertexList[2]를 잇는 직선을 그리고, pVertexList[2]~pVertexList[3], ..., pVertexList[dwVertexListCount-2]~pVertexList[deVertexListCount-1]을 잇는 직선까지 주루룩 그린다.
dwVertexListCount는 예상했던것처럼, 점들의 숫자이다.
Color는 당연히 직선의 색깔!

근데, 이것만으로는 좀 밋밋하다. 직선 굵기도 바꾸고 모양도 바꾸고 하고 싶은데... 그건 어떻게 해야할까

SetWidth(float 선의 굵기)
SetPattern(DWORD 패턴)
SetPatternScale(flaot 패턴의 스케일)
등등의 함수들이 준비되어 있다. 근데 이런 직선의 특징을 바꾸는 함수는 Begin함수가 시작되기 전에 호출되어야 한다. 즉, 이미 Begin함수가 호출되었으면, 직선의 특징을 바꿀수 없다는 것이다. 이 점 유의하자.

SetPattern함수를 자세히 살펴보자. 패턴을 지정해줄수 있는데 어떻게 지정해 줄수 있을까?
패턴 지정은 비트를 이용해서 한다. DWORD는 32비트를 가진다. 우리는 이 32개의 비트를 1 또는 0으로 설정해가면서 패턴의 모양을 만들수 있다. 1은 칠해지는거고, 0은 안 칠해지는 거다.

꽉찬 직선: 11111111111111111111111111111111 (0xFFFFFFFF)
점선(. . . . . . ): 10101010101010101010101010101010 (0x55555555)
파선(- - - - - ): 11110000111100001111000011110000 (0x0F0F0F0F)
쇄선(- . - . - .): 11110010111100101111001011110010 (0x4F4F4F4F)
(비트 배열은 최하위비트에서 최상위비트순이다.)

예를 들어서 몇개만 대충 써봤는데, 2^32가지 무늬가 나올수 있으니 자유롭게 생각해보시라요.
SetPattern함수로 패턴을 지정했으면, SetPatternScale함수로 꼭 스케일을 지정해 주자. 1.0이 기본스케일이고, 커질수록 패턴전체가 길어지고, 작아질수록 패턴이 짧아진다.

이 정도 지식이면 직선그리는거는 문제 없다. GGame클래스에 LPD3DXLINE m_pline을 멤버변수로 추가하자.


Create3DDevice함수에서 D3DXCreateLine함수를 호출해주고,
직선을 그리는 DrawLine함수를 추가해주자.


참, 쉽죠?

자, 이제 사각형 그리기로 넘어간다.
불행히도 D3D에서는 사각형을 편리하게 그리는 인터페이스를 제공해주지 않는다.
컴퓨터가 3D를 그릴때 삼각형을 기본단위로 한다. 그래서 삼각형의 기본이 되는 세 점과 삼각형 정보를 토대로 모든 모델들이 만들어진다. D3D에서 각각의 이런 점들을 정점(Vertex)라고 부른다. 정점이 3개 모이면 삼각형 하나를 그릴수 있다. 정점은 여러가지 정보를 가질 수 있다. 가장 기초적인 위치정보(x,y,z)에서 색깔(diffuse color), 법선(normal), 텍스쳐좌표(u, v) 등등등.
D3D에서는 이런 여러가지 정보들을 자유롭게 선택해서 고를수 있게 해 놓았다. 이름하여 Flexible Vertex Format, 줄여서 FVF라고 한다.
D3D에서 정점 배열을 가지고 삼각형들을 그리는 단계는 다음과 같다.
1. IDirect3DDevice9::SetFVF로 FVF를 설정한다.
2. IDirect3DDevice9::DrawPrimitiveUp으로 그린다.

사각형을 그리는 데에는 위치정보하고 색깔 정도만 있으면 된다.

m_pd3dd->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
D3DFVF_XYZRHW는 정점이 위치정보(x, y, z, w)를 가진다는 것을 나타낸다. 그리고 D3DFVF_XYZRHW로 설정된 위치정보들은 행렬변환을 거치지 않는다.
D3DFVF_DIFFUSE는 색깔(diffuse color)을 가진다는 것을 나타낸다.

이제, 이 포맷에 맞춰서 정점 배열을 만들어야 한다.
struct Vertex{
 float x, y, z, w; // D3DFVF_XYZRHW 위치정보
 D3DCOLOR color; // D3DFVF_DIFFUSE 색깔
};
Vertex v[4]={
 {x1, y1, 1.f, 1.f, color1}, //z에는 아무값이 들어가도 상관없다.
 {x2, y2, 1.f, 1.f, color2}, //w에도 아무값이 들어가도 상관없지만, 4개의 w값이 다 같아야 한다.
 {x3, y3, 1.f, 1.f, color3},
 {x4, y4, 1.f, 1.f, color4},
}; //정점 4개 배열을 만든다.
m_pd3dd->DrawPrimitiveUp(D3DPT_TRIANGLESTRIP, 2, v, sizeof(Vertex)); // 정점 배열을 가지고 삼각형들을 그린다.

HRESULT DrawPrimitiveUP( D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride );
PrimitiveType은 정점으로 무엇을 그릴것인지를 결정한다. 점을 그릴 수도 있고, 선을 그릴 수도 있고, 삼각형을 그릴수도 있고, 여러 가지가 있지만 우리는 연결된 삼각형들을 그릴것이므로 D3DPT_TRIANGLESTRIP을 사용한다.
PrimitiveCount는 그릴 도형의 갯수이다. 우리는 삼각형 2개를 그릴것이므로 2라고 한다.
pVertexStreamZeroData는 정점 배열의 포인터이다. v를 넣어준다.
VertexStreamZeroStride는 정점 하나의 크기(바이트수)이다. sizeof(Vertex)를 넣어준다.
이렇게 하면 아마 아래와 같은 도형이 출력될 것이다.
D3DPT_TRIANGLESTRIP은 위처럼 v[0], v[1], v[2]를 이어서 하나의 삼각형을 만들고, 또 v[1], v[2], v[3]을 이어서 하나의 삼각형을 만들고,... v[n-1], v[n], v[n+1]을 이어서 하나의 삼각형을 만들어, 총 n개의 삼각형을 그린다.
마찬가지로 삼각형을 그리지만, D3DPT_TRIANGLELIST는 v[0], v[1], v[2]를 이어서 하나의 삼각형을 만들고, v[3], v[4], v[5]를 이어서 하나의 삼각형을 만들고, ... v[3n-3], v[3n-2], v[3n-1]를 이어서 하나의 삼각형을 만들어, 총 n개의 삼각형을 그린다.

자, 이제 직접 구현해보자.


별로 특별하게 어려운 부분은 없다.
근데 그리기전에 m_pspr->End를 호출해주는것. 중요하다.
ID3DXSprite는 내부적으로 우리가 사각형을 그리는 과정과 비슷하게 구현되어있고, IDirect3DDevice9의 여러 상태를 변화시킨다. 그렇기에 ID3DXSprite로 그리고 있는 상태에서 또 IDirect3DDevice9를 사용하여 사각형을 그리려고 하면 충돌이 발생할 수 있다. (원하는 대로 사각형이 그려지지 않을 것이다.) 그러므로 IDirect3DDevice9로 그리기를 할때에는 ID3DXSprite로 그리던것을 마쳐야 한다.
그리고 함수 끝 부분에서 다시 ID3DXSprite::Begin함수를 호출하여 다시 스프라이트 그리기를 준비한다.

또 하나 눈여겨봐야 할 점은 사각형 좌표에서 0.5씩을 빼주었다는 것이다. 이 0.5를 마법의 숫자라고도 부른다. D3D의 좌표체계는 2D처럼 단순하지 않다. x,y,z좌표가 실수이기 때문에, 단순히 정수 하나가 1픽셀에 대응하던것과는 다르게 작동된다. 2D에서 하던것처럼 (5, 5)에 점을 하나 찍으려고 하면, 점은 주변 픽셀 4개에 걸쳐서 찍혀진다. 하지만, 0.5을 빼줘서 (4.5, 4.5)에 점을 하나 찍으면 원하는 픽셀에 딱 맞게 찍혀진다.


OnDraw함수에서 이번에 만든 함수들을 한 번 써먹어보자.

사각형의 그라데이션이 참 아름답다.

관련글 더보기

댓글 영역