Windows programming is like going to the dentist: You know it's good for you, but no one likes doing it. -- Andre LaMothe, Tricks of the Windows Game Programming Gurus
* 원제목 : 2D Rendering in DirectX 8(GameDev.net)
- 원저자 : Kelly Dempski
- 원문링크 :
http://www.gamedev.net/reference/articles/article1434.asp
- 현재는 직역상태입니다. 제가 읽어봐도 (읽는 사람입장에서는) 너무 난해하게 적어져 있습니다. -_-; 노동부분들은 약간만 참아주세요~
1 이 글의 배경 #
나는 DirectX 8과 신규 API에서 DirectDraw가 제외된 점에 대한 수많은 질문들을 읽어본 적이 있다. 많은 사람들이 DX7으로 돌아가버리는 현상을 보였다. 나는 DX7를 이전에 많이 사용해본 경험이 있으면서 DX7을 사용하는 사람들은 이해할 수 있다. 허나, 대부분의 질문들은 이제 막 DX를 배우려고 하는 사람들로부터 오는 것이 보통이었고, 그들은 이전 DX7 API를 공부하는데 열중해 있었다. 사람들은 대부분의 사람들이 3D 하드웨어를 가지고 있지 않다고 논쟁을 벌였고, 그러므로 D3D이 DirectDraw에 대한 대체수단이 되는 것이 나쁘다고 여겼다. 나는 이것이 맞는 것인지 믿을 수 없다. D3D상에서의 2D 랜더링은 거의 정점 관리가 필요하지 않고, 다른 모든 것은 결국 갱신율(fillrate)로 요약된다.
dx가 버젼이 8로 올라가면서 가장 큰 변화는 하위 호환을 포기하면서까지도 성능향상을 시도했다는 점이다. ddraw가 사라지고 3d 인터페이스 상에서 2d를 처리하게되는 상황이 된 것도 큰 변화이다. 물론, COM객체구조 덕분에 버전 7의 인터페이스가 여전히 살아있지만(실행화일은 전혀 무리없이 실행된다.) dx8 SDK에서는 아쉽게도 dx7 소스의 대부분이 컴파일되지 않는다. 이 때문에 dx8을 사용하려면 지금은 예전의 블럭전송(blit)대신 택스쳐와 사각 폴리곤으로 그림을 표현해야만 한다. 이 폴리곤을 사용하는 방법은 이전 방법에 비해 다음과 같은 장점이 있다.
- 회전, 크기조절, 찌그러트리기등 변환이 자유롭다.
- 만일 3D 가속을 비디오카드가 지원한다면 훨씬 향상된 출력을 기대할 수 있다.
- 알파블랜딩을 적용하는 것이 다소 이전방법(dx7)보다 간단하다.
- 메모리를 비교적 많이 차지하게 된다. (8bit 택스쳐도 있지만 이 것을 다루는 것은 그렇게 쉬운 일이 아니다.)
- 기본 이미지(택스쳐로 사용됨)를 정사각형 형태로 제공해야만 한다. (최신 카드는 그렇지 않아도 되나, 이것은 게이머들이 일반적으로 사용하기에는 아직 너무 비싸다.)
- 싫어도 3D에 대한 기본 개념정도는 알고 있어야 한다.
2 시작 #
여러분이 DX8 SDK를 가지고 있다면, D3D 디바이스를 생성하는 법과 랜더링(메세지) 루프를 설정하는 몇가지 튜토리얼들이 있을 것이다. 이 정도는 이미 알고 있다고 가정하겠다. [DX8SDK]\samples\Multimedia\Direct3D\Tutorials\Tut01_CreateDevice에 있는 예제 소스를 고쳐가면서 코드를 만들어가보도록 할 것이다. 자, 이제 다음코드를 전역변수 선언 부 바로 밑에 선언해둔다.
void PostInitialize(float WindowWidth, float WindowHeight)
{
}
void Render2D()
{
}
void PostInitialize(float WindowWidth, float WindowHeight) - 이 함수는 다른 모든 것에 대한 셋업이 끝난 이후에 어플리케이션에 의해 호출된다. 여러분은 이제 디바이스를 생성하고 모든 것을 초기화한 상태에 있다. 만약 튜토리얼 코드를 따라오고 있는 중이라면, WinMain은 다음과 비슷하게 보일 것이다:
...
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
PostInitialize(200.0f, 200.0f); // 이것이 추가된 소스 라인이다. 200.0f는 {{{CreateWindow() 함수의 호출때
// 사용된 윈도우 크기에 따라 정해진 수치이다.
ShowWindow( hWnd, SW_SHOWDEFAULT );
...
}}}
void Render2D() - 이 함수는 여러분의 장면하나하나를 랜더링 할 때마다 호출된다. 튜토리얼상에서의 Render함수를 보면 다음과 비슷하게 쓰여져 있을 것이다:
VOID Render()
{
if( NULL == g_pd3dDevice )
return;
// 백버퍼를 파란색으로 채운다.
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
// 장면 랜더링 시작
g_pd3dDevice->BeginScene();
Render2D(); // 추가된 소스라인이다.
// 장면 랜더링 끝
g_pd3dDevice->EndScene();
// 화면 디스플레이로 백버퍼 내용을 나타낸다.
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
자, 이것이 어플리케이션의 겉겁데기이다. 이제 이 쓸만한 것을 다루어보자~
3 D3D상에서 2D 그리기 작업을 위한 설정 #
주의 : 여기는 D3D가 끌어들인 몇가지 고약한 수학에 대해서 이야기하기 시작하는 곳이다. 괜히 겁먹거나 경계할 필요는 없다 - 원한다면, 상세한 내용의 대부분을 무시해도 상관없다.
대부분의 D3D 그리기 작업은 프로젝션 행렬, 월드 행렬, 뷰 행렬의 3가지 배열에 의해 조정된다.
우선 프로젝션 행렬에 대해서 이야기 해보도록 하자. 프로젝션 행렬은 여러분의 카메라의 렌즈에 대한 속성을 결정하는 것으로써 생각할 수 있다. 3D 어플리케이션상에서는, 원근법(perspective)을 사용할 것인지등등을 정의할 수 있다. 그러나, 우리는 원근법을 원하지 않는다 - 우리는 지금 2D에 대해서 얘기하고 있기 때문이다! 그러므로 평행(orthogonal) 프로젝션이 대해서 이야기하고자 한다. 짧게 요약하자면, 이것은 3D 그리기 작업의 부가된 설정없이 2D를 그릴 수 있게 해준다. 평행프로젝션 행렬을 생성하기 위해서, D3DXMatrixOrthoLH를 호출할 필요가 있다.
다른 행렬들(뷰와 월드)는 카메라의 위치와 월드의 위치(또는 월드내의 객체)를 정의한다. 2D 작업에 있어서는 카메라와 월드를 움직일 필요가 없다. 그러므로 우리는 카메라와 월드를 기본위치에 설정하기 위해 항등(identity) 행렬을 사용할 것이다. D3DXMatrixIdentity을 호출하여 항등행렬을 생성할 수 있다.
D3DX 함수들을 사용하기 위해서는, 소스에 다음 라인을 추가해야한다 :
#include <d3dx8.h>그리고 라이브러리 링크 설정에 d3dx8dt.lib을 추가한다. 설정이 모두 끝났으면, PostInitialize함수를 다음과 같이 정의해라 :
void PostInitialize(float WindowWidth, float WindowHeight)
{
D3DXMATRIX Ortho2D;
D3DXMATRIX Identity;
D3DXMatrixOrthoLH(&Ortho2D, WindowWidth, WindowHeight, 0.0f, 1.0f);
D3DXMatrixIdentity(&Identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &Ortho2D);
g_pd3dDevice->SetTransform(D3DTS_WORLD, &Identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &Identity);
}
우리는 이제 2D작업을 위한 설정을 끝냈고, 이제 그리기위해서는 무언가가 더 필요하다. 앞에서 설정한데로 라면, 우리의 그리기 영역은 (-WindowWidth/2, WindowWidth/2)에서 (-WindowHeight/2 , WindowHeight/2)에 해당하는 사각영역에 해당한다. 이 코드상에서 주의해야 할 한가지는 폭과 높이는 픽셀단위라는 것이다. 이것은 모든 것을 픽셀단위의 입장에서 생각하도록 만든다. 허나 우리는 폭과 넓이를 1.0으로 놓도록 할 수도 있고 특정한 크기를 정의할 수 있는 등등의 설정을 할 수도 있다. 스크린 공간을 퍼센트 비율로 보게 되면, 다중 해상도를 지원하는 것이 얼마나 쉬울지를 알 수 있다. Changing the matrix allows for all sorts of neat things, but for simplicity, we'll talk about pixels for now?
4 2D "판넬"을 설치하기 #
내가 2D개념하에서 그리기 작업을 할 때에는, 2D 사각형을 그리는 데 필요한 모든 것을 추상화시킨 CDX8Panel이라고 불리는 클래스를 사용했다. C++ 설명을 하지않고 간단하게 설명하기위해, 여기에 코드를 첨부한다. 어쨌거나, 여러분은 C++을 사용하지 않는다면, 아마도 클래스 혹은 더 고수준의 API의 값어치를 접하게 될 것이다.
나는 앞서설명한 것들이 동작하는 방법을 보여주기 위해 기본사항들을 여기서 설명하려한다. 그러나, 여러분의 필요에 맞게 스프라이트 인터페이스를 사용하는 것만을 원할 수도 있다.
판넬에 대한 정의는 단순히 화면에 그려질 2D 택스쳐 사각형이다. 판넬을 그리는 것은 2D blit와 엄청나게 유사하다. 숙련된 2D 프로그래머는 이것이 blit를 위한 많은 수의 작업이라고 생각할 수 있다. 그러나, 이 작업은 사용가능한 수많은 효과에 대한 작업량을 절약해준다. 첫째, 우리는 우리의 사각형의 기하학 정보에 대하여 생각해야만 한다. 이것은 정점들에 대한 개념들과 연관되어있다. 만약 3D 하드웨어를 가지고 있다면, 하드웨어는 아주 빠르게 정점들을 처리할 것이다. 2D 하드웨어를 가지고 있다면, 정점처리에 대해서는 거의 얘기가 없을 것이고, CPU가 모든 것을 재빨리 처리하게 될 것이다. 자, 우리의 정점 포맷을 정의할 차례이다. 다음코드를 #include를 기술하는 부분 근처에 기술해라 :
struct PANELVERTEX
{
FLOAT x, y, z;
DWORD color;
FLOAT u, v;
};
#define D3DFVF_PANELVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)
이 구조체와 융통성있는 정점포맷(?! Flexible Vertex Format - FVF : 아마도 float형으로 선언된 정점좌표데이타를 말하는 것인듯.)은 정점의 위치, 색상, 택스쳐 좌표들을 정의한다.
이제 정점버퍼가 필요한 순간이다. 다음 소스코드를 전역변수 선언부에 넣도록 하라.
LPDIRECT3DVERTEXBUFFER8 g_pVertices = NULL;지금, PostInitialize함수에 다음 코드를 추가하라. :
float PanelWidth = 50.0f;
float PanelHeight = 100.0f;
g_pd3dDevice->CreateVertexBuffer(4 * sizeof(PANELVERTEX), D3DUSAGE_WRITEONLY,
D3DFVF_PANELVERTEX, D3DPOOL_MANAGED, &g_pVertices);
PANELVERTEX* pVertices = NULL;
g_pVertices->Lock(0, 4 * sizeof(PANELVERTEX), (BYTE**)&pVertices, 0);
//모든 색상을 흰색으로 설정한다.
pVertices[0].color = pVertices[1].color = pVertices[2].color = pVertices[3].color = 0xffffffff;
//위치와 택스쳐 좌표를 설정한다.
pVertices[0].x = pVertices[3].x = -PanelWidth / 2.0f;
pVertices[1].x = pVertices[2].x = PanelWidth / 2.0f;
pVertices[0].y = pVertices[1].y = PanelHeight / 2.0f;
pVertices[2].y = pVertices[3].y = -PanelHeight / 2.0f;
pVertices[0].z = pVertices[1].z = pVertices[2].z = pVertices[3].z = 1.0f;
pVertices[1].u = pVertices[2].u = 1.0f;
pVertices[0].u = pVertices[3].u = 0.0f;
pVertices[0].v = pVertices[1].v = 0.0f;
pVertices[2].v = pVertices[3].v = 1.0f;
g_pVertices->Unlock();
보이는 것에 비해 이 코드의 내용은 정말로 간단하다. 먼저 첫째로, 판넬의 크기를 지정했다. 다음에는 지정한 포맷에 따른 4개의 정점이 담길만한 정점 버퍼를 생성하도록 디바이스에 요청했다. 그러면 버퍼는 잠기고(lock) 값을 설정할 수 있는 상태가 된다. 주의해야 할 한가지는, 버퍼를 잠그는 작업은 매우 비싼 - 실행시간, 부하가 많이 드는 - 작업이라는 점이다. 그러므로 단 한번만 잠그고 모든 작업을 처리하는 것이 좋다. 버퍼를 잠그는 과정없이 정점들을 다룰수도 있는데, 이것은 나중에 논하도록 하자. 이 예에서는 원점을 중심으로 한 4개의 정점을 설정했다. 이것을 기억해두자 - 나중에 이것을 활용하게 될 것이다. 또한, 택스쳐 UV 좌표도 설정했다. SDK에 이것에 대해 잘 설명되어있으므로 이것에 대해서는 언급하지 않겠다. 간단히 말하면, 여기서는 전체 택스쳐를 그리도록 설정되어있다. 자, 이제 우리는 설정이 끝난 사각형을 얻었다. 다음은 이것을 그리는 단계이지 않을까?
5 판넬을 그리기 #
판넬을 그리는 것은 정말로 쉽다. 다음 코드를 Render2D에 추가하라 :
g_pd3dDevice->SetVertexShader(D3DFVF_PANELVERTEX); g_pd3dDevice->SetStreamSource(0, g_pVertices, sizeof(PANELVERTEX)); g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);이 소스라인들은 디바이스에게 정점이 어떻게 포맷되어있고, 어떤 정점들을 사용할 것이며, 정점들이 어떻게 사용될 것인가를 알려준다. 나는 이것을 triangle fan 방식으로 그리는 것을 선택했다. 왜냐면 2개의 삼각형을 그리는 것보다 더 작기 때문이다 - 그냥 그리려면 6개의 정점이 필요하지만, triangle fan 방식은 4개면 된다. 여기서 다른 정점 포맷이나 정점 버퍼들을 다루지 않을 것이기 때문에, PostInitialize 함수의 Note that since we are not dealing with other vertex formats or other vertex buffers, we could have moved the first two lines to our PostInitialize function. I put them here to stress that you have to tell the device what it's dealing with. 만약하지 않는다면, 정점들이 다른 포맷인 것으로 간주하고 충돌을 일으킬 수 있다. 이 시점에서 컴파일하고 코드를 실행할 수 있다. 모든 것이 정확하다면, 여러분은 파란색 바탕의 검정 사각형을 볼 수 있을 것이다. This isn't quite right because we set the vertex colors to white. 문제는 디바이스가 광원을 켜놓았다는 것에 있다. PostInitialize()함수에 다음 라인을 추가하여 광원효과설정을 꺼놓자:
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);이제, 재컴파일해보면 디바이스가 정점 컬러를 사용하고 있다는 것을 알 수 있을 것이다. 원한다면 정점의 색상을 바꿀 수 있으며, 나름대로의 효과를 볼 수 있을 것이다. 흰색 사각형 배경의 게임은 재미없으므로 다음에는 택스쳐를 추가해보기로 한다.
6 판넬에 택스쳐를 입히기 #
택스쳐는 화일이나 특정 데이타로 부터 읽어들여진 비트맵을 의미한다. 여기서는 단순하게 하기위해 화일로부터 읽어들여 설정하도록 한다. 다음 코드를 전역 변수로 선언한다:
LPDIRECT3DTEXTURE8 g_pTexture = NULL;이것은 우리가 앞으로 사용할 택스쳐 객체이다. 택스쳐를 화일로부터 읽어들일려면 다음 소스부분을 PostInitialize함수에 추가한다:
D3DXCreateTextureFromFileEx(g_pd3dDevice, [Some Image File], 0, 0, 0, 0,
D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
D3DX_DEFAULT , 0, NULL, NULL, &g_pTexture);
Some Image File은 여러분이 선택할 그림화일이다. D3DX 함수는 많은 표준 포맷들을 지원한다. (역주 : .bmp, .dds, .dib, .jpg, .png, .tga 화일들을 지원한다.) 우리가 사용할 픽셀포멧은 알파채널을 가지고 있으므로 .dds화일과 같이 알파채널이 지원되는 포맷을 읽어야 할 것이다. 또한, 컬러키 인자는 무시할 것이다. 그러나 투명을 위한 색은 여전히 지정해주어야한다. 여기서는 투명처리를 조금 다르게 처리할 것이다. 지금까지, 택스쳐를 하나 가졌고, 이미지를 읽어들였다. 이제 디바이스에게 이것을 사용하게 하자. Render2D의 첫 줄에 다음 코드를 넣는다:
g_pd3dDevice->SetTexture(0, g_pTexture);이 소스는 텍스쳐를 사용하여 삼각형들을 랜더링한다고 디바이스에게 알려준다. 여기서 기억해두어야 할 한가지 중요한 점은 소스를 간단히 하기 위해서 에러 검사를 하지 않았다는 것이다. 텍스쳐가 사용되기 전에 실제로 로딩이 성공적으로 이루어졌는지 검사하는 알맞은 에러검사루틴을 추가하는 것이 좋을 것이다. 쉽게 발생하는 에러중 하나는 텍스쳐의 해상도는 대부분의 하드웨어에서는 64x64, 128x512처럼 2의 거듭제곱으로 지정이 되어야하는 점이다. (아주 최신의 nVidia 카드에서만 이 룰이 깨진다. 안전하게 하기위해서는 그냥 2의 거듭제곱 해상도를 사용하는 것이 좋다.) 자, 이제 컴파일하고 실행하보면 이미지가 매핑된 직사각형을 볼 수 있을 것이다.
7 택스쳐 좌표 #
직사각형에 맞추기 위해 택스쳐가 찌그러지거나 늘려졌다는 것에 주목해라. 텍스쳐 좌표를 조정함으로써 찌그러짐의 정도를 조정할 수 있다.
Note that it is stretched/squashed to fit the rectangle. You can adjust that by adjusting the texture coordinates. For example, if you change the lines where u = 1.0 to u = 0.5, then only half of the texture is used and the remaining part will not be squashed. So, if you had a 640x480 image that you wanted to place on a 640x480 window, you could place the 640x480 image in a 1024x512 texture and specify 0.625, 0.9375 for the texture coordinates. You could use the remaining parts of the texture to hold other sub images that are mapped to other panels (through the appropriate texture coordinates). In general, you want to optimize the way textures are used because they are eating up graphics memory and/or moving across the bus. This may seem like a lot of work for a blit, but it has a lot to do with the way new cards are optimized for 3D (like it or not). Besides, putting some thought into how you are moving large chunks of memory around the system is never a bad idea. But I'll get off my soapbox.
Let's see where we are so far. At one level, we've written a lot of code to blit a simple bitmap. But, hopefully you can see some of the benefit and the opportunities for tweaking. For instance, the texture coordinates automatically scale the image to the area we've defined by the geometry. There are lots of things this does for us, but consider the following. If we had set up our ortho matrix to use a percentage based mapping, and we specified a panel as occupying the lower quarter of the screen (for a UI, let's say), and we specified a texture with the correct texture coordinates, then our UI would automagically be drawn correctly for any chosen window/screen size. Not exactly cold fusion, but it's one of many examples. Now that we have the texture working well, we have to get back to talking about transparency.
8 투명처리 #
이전에 언급했듯이, 투명 효과를 덧붙이기 위한 한가지 쉬운 방법은 D3DXCreateTextureFromFileEx()를 호출하여 컬러 키를 정의하는 것이다. 또다른 방법은 알파채널을 가지고 있는 이미지를 사용하는 것이다. 양쪽 방법 어떤 것이라도 사용해서, 몇가지 투명설정을 가진 택스쳐를 정의하고 어플리케이션을 실행해보아라. 설정하기 전과 차이가 없다는 것을 알 수 있을 것이다. 이것은 알파 블랜딩이 켜져있지 않기 때문이다. 알파 블랜딩을 켜기 위해서 다음 소스코드를 PostInitialize()에 추가하라 :
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);첫번째 라인은 알파 블랜딩을 켠다. 두번째 라인은 어떻게 블랜딩이 동작할 것인지를 정의한다. 지정할 수 있는 많은 형태가 가능하지만, 여기서는 가장 기본적인 형식을 사용하기로 한다. The last line sets things up so that changing the alpha component of the vertex colors will fade the entire panel by scaling the texture values. 설정가능한 옵션에 대해 더 자세히 알고 싶다면 SDK를 참조하기 바란다. 이 소스라인을 한번 위치시키고 실행해보면, 알맞은 투명효과를 볼 수 있을 것이다. 정점의 색상을 변경하여 판넬에 어떻게 영향을 미치는지 보도록 해라.
9 판넬을 움직이기 #
지금까지 우리의 판넬에 상당수의 비쥬얼 속성들을 설정했지만, 아직 판넬은 뷰포트의 중앙에 아직 짱박혀있다. 게임개발을 목적으로 하고있다면 판넬이 움직이는 것을 원할 것이다. 확실한 한가지 방법은 정점을 다시 잠그고(relock) 그것들의 위치를 바꾸는 것이다. 이것을 절대로 하지 말아라!!! 잠금(lock)은 매우 비싼 처리이고 데이타의 이동이 실행되는 것은 불필요한 처리이다. 더 좋은 방법은 정점들을 움직이기위한 월드 변환 행렬을 정의하는 것이다. 많은 사람들이 행렬에 대해서 조금 두려워하는 경향이 있지만, 행렬들을 만드는 것을 쉽게 해주는 D3D 함수들이 있다는 것을 알아두기 바란다. 예를 들자면, 판넬을 움직이기 위해서는 Render2D()함수의 처음에 다음과 같이 코드를 추가하면 된다:
D3DXMATRIX Position; D3DXMatrixTranslation(&Position, 50.0f, 0.0f, 0.0f); g_pd3dDevice->SetTransform(D3DTS_WORLD, &Position);이것은 X 축 방향으로 판넬을 50 픽셀만큼 움직이는 행렬을 생성하며, 디바이스에게 변환을 적용하도록 시킨다. 이것은 MoveTo(X, Y)같은 함수로 포장할 수도 있겠지만 여기서는 하지 않겠다. (스스로 해봐라.) 더 이전에 정점 코드는 원점 주위의 정점들을 정의한다고 설명했었다. 이 설명에 따르면, 위치를 이동하는 것은 판넬의 중심을 이동시키는 것과 같다. 왼쪽위나 다른 위치로 이동하는 것에 익숙해졌다면, 정점들을 정의하는 방법을 바꾸어봐라. 여러분은 MoveTo(X, Y)함수의 두 인자를 사용하여 보정함으로써 또한 다른 좌표계를 생성할 수도 있다. 예를 들자면 우리의 뷰포트는 현재 -100에서 +100까지 펼쳐져있다. 만약 MoveTo(X, Y)를 사용하게 되었다면, 좌표계는 0에서 200으로 지정한 것처럼 될 것이다. I could simply correct for it in my call to D3DXMatrixTranslation by subtracting 100 from the X position. 여러가지 용도에 맞게 빠르게 이것을 변경하는 많은 방법이 존재한다. 하지만, 실험하는데 있어서 이 코드는 탄탄한 기초 역할을 해줄거라 생각한다.
10 다른 행렬 연산들 #
판넬에 영향을 줄 수 있는 다른 많은 행렬 연산들이 존재할 수 있다. 아마도 가장 흥미 있는 것은 확대/축소와 회전일 것이다. D3D 함수들 중 이런 행렬을 쉽게 생성하는 함수들이 존재한다. 더이상 예제를 보여줄 수는 없지만 힌트를 주도록 하겠다. Z 축에 대해서 회전을 시키면 화면 기준에서 회전을 하는 효과가 있다. X나 Y축으로 회전을 하면 Y나 X축 방향으로 찌그러지는 효과를 볼 수 있다. 또한, 적당한 순서대로 행렬들을 곱함으로써 다양한 연산을 만들 수 있다:
D3DXMATRIX M = M1 * M2 * M3 * M4; g_pd3dDevice->SetTransform(D3DTS_WORLD, &M);그러나 기억해야 할 것은 행렬 곱은 교환법칙이 성립하지 않는다는 것이다. (즉, 순서가 바뀌면 다른 효과가 된다.) 예를 들면, 회전 * 위치이동은 판넬을 이동하고 제자리에서 회전한다. 하지만, 평행이동 * 회전은 위성이 도는 것같은 운동을 하게 된다. 만약 몇몇개의 행렬을 조합하고 원하지 않은 결과를 얻었다면, 행렬의 순서를 다시 한번 잘 보도록 해라.
좀더 익숙해짐에 따라, 텍스쳐 좌표를 변형시키는 역할을 하는 텍스쳐 행렬과 같은 것을 경험하려 할 수도 있다. 또한 좌표계에 영향을 미치는 뷰 행렬을 이동시키려 할 수도 있을 것이다. 하나만 명심하도록 해라 : 잠금(lock)은 매우 비싼 처리이다. 언제나 정점 버퍼를 잠그기전에 행렬과 같은 것들을 잊었는지 살펴보도록 해라.
11 마무리 #
여기서 열거한 코드들을 모두 살펴보았다면, 이것은 blit작업을 위한 방법치고는 정말로 길고, 오래끈다는 것을 알 수 있을 것이다. 그러나 여기서의 자그마한 함수들 혹은 클래스들로 포장하는 작업들이 한번의 수고로 얻을 수 있는 장기적인 잇점을 위한 것이라면 정말로 괜찮은 것이라 말할 수 있다. 여기서 설명한 것은 정말로 순수 뼈대이며 최적화되지 않은 방법이라는 점을 기억하기 바란다. 최대한의 성능이익을 얻기 위한 패키징하는 방법은 얼마든지 존재할 수 있다. 이 방법은 현재로서는 2D 어플리케이션을 생성하는 최선의 방법이 될 수도 있다. 또한 구현작업을 쉽게 해준다는 측면에서 개발에 따른 노력을 아낄 수 있을 것이다. 이 접근법은 또한 2D와 3D를 섞는 것을 도와줄 수 있다. (왜냐하면 몇가지 행렬의 입장에서 보았을때 둘은 같기 때문이다.) 코드는 내가 OpenGL에서 했던 2D 작업으로 쉽게 바꾼 적이 있다. 그러므로 OpenGL과 DX3D 양쪽을 모두 지원하는 추상적 래퍼(abstract wrapper)를 작성할 수도 있을 것이다. 나의 바램은 이 아티클이 2D 게임을 만들기 위해서 사람들로 하여금 DX8을 공부하는 방향으로 돌렸으면 하는 것이다. 아마도 나중에는 좀더 많은 트릭과 효과들에 대해서 이야기 할 예정이다.








